winamp/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeCommon.h

680 lines
16 KiB
C
Raw Normal View History

2024-09-24 14:54:57 +02:00
/*
* BridgeCommon.h
* --------------
* Purpose: Declarations of stuff that's common between the VST bridge and bridge wrapper.
* Notes : (currently none)
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#pragma once
#include "openmpt/all/BuildSettings.hpp"
#include <vector>
#if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
#include <intrin.h>
#endif
OPENMPT_NAMESPACE_BEGIN
// Insert some object at the end of a char vector.
template <typename T>
static void PushToVector(std::vector<char> &data, const T &obj, size_t writeSize = sizeof(T))
{
static_assert(!std::is_pointer<T>::value, "Won't push pointers to data vectors.");
const char *objC = reinterpret_cast<const char *>(&obj);
data.insert(data.end(), objC, objC + writeSize);
}
static void PushZStringToVector(std::vector<char> &data, const char *str)
{
if(str != nullptr)
data.insert(data.end(), str, str + strlen(str));
PushToVector(data, char(0));
}
OPENMPT_NAMESPACE_END
#include "AEffectWrapper.h"
#include "BridgeOpCodes.h"
OPENMPT_NAMESPACE_BEGIN
// Internal data structures
// Event to notify other threads
class Event
{
private:
HANDLE handle = nullptr;
public:
Event() = default;
~Event() { Close(); }
// Create a new event
bool Create(bool manual = false, const wchar_t *name = nullptr)
{
Close();
handle = CreateEventW(nullptr, manual ? TRUE : FALSE, FALSE, name);
return handle != nullptr;
}
// Duplicate a local event
bool DuplicateFrom(HANDLE source)
{
Close();
return DuplicateHandle(GetCurrentProcess(), source, GetCurrentProcess(), &handle, 0, FALSE, DUPLICATE_SAME_ACCESS) != FALSE;
}
void Close()
{
CloseHandle(handle);
}
void Trigger()
{
SetEvent(handle);
}
void Reset()
{
ResetEvent(handle);
}
void operator=(const HANDLE other) { handle = other; }
operator const HANDLE() const { return handle; }
};
// Two-way event
class Signal
{
public:
Event send, confirm;
// Create new (local) signal
bool Create()
{
return send.Create() && confirm.Create();
}
// Create signal from name (for inter-process communication)
bool Create(const wchar_t *name, const wchar_t *addendum)
{
wchar_t fullName[64 + 1];
wcscpy(fullName, name);
wcscat(fullName, addendum);
fullName[std::size(fullName) - 1] = L'\0';
size_t nameLen = wcslen(fullName);
wcscpy(fullName + nameLen, L"-s");
bool success = send.Create(false, fullName);
wcscpy(fullName + nameLen, L"-a");
return success && confirm.Create(false, fullName);
}
// Create signal from other signal
bool DuplicateFrom(const Signal &other)
{
return send.DuplicateFrom(other.send)
&& confirm.DuplicateFrom(other.confirm);
}
void Send()
{
send.Trigger();
}
void Confirm()
{
confirm.Trigger();
}
};
// Memory that can be shared between processes
class MappedMemory
{
protected:
struct Header
{
uint32 size;
};
HANDLE mapFile = nullptr;
Header *view = nullptr;
public:
MappedMemory() = default;
~MappedMemory() { Close(); }
// Create a shared memory object.
bool Create(const wchar_t *name, uint32 size)
{
Close();
mapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size + sizeof(Header), name);
if(!mapFile)
{
return false;
}
view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0));
if(!view)
{
return false;
}
view->size = size;
return Good();
}
// Open an existing shared memory object.
bool Open(const wchar_t *name)
{
Close();
mapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, name);
if(!mapFile)
{
return false;
}
view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0));
if(!view)
{
return false;
}
return Good();
}
// Close this shared memory object.
void Close()
{
if(mapFile)
{
if(view)
{
UnmapViewOfFile(view);
view = nullptr;
}
CloseHandle(mapFile);
mapFile = nullptr;
}
}
template <typename T = void>
T *Data() const
{
if(view == nullptr)
return nullptr;
else
return reinterpret_cast<T *>(view + 1);
}
size_t Size() const
{
if(!view)
return 0;
else
return view->size;
}
bool Good() const { return view != nullptr; }
// Make a copy and detach it from the other object
void CopyFrom(MappedMemory &other)
{
Close();
mapFile = other.mapFile;
view = other.view;
other.mapFile = nullptr;
other.view = nullptr;
}
};
// Bridge communication data
#pragma pack(push, 8)
// Simple atomic value that has a guaranteed size and layout for the shared memory
template <typename T>
struct BridgeAtomic
{
public:
BridgeAtomic() = default;
BridgeAtomic<T> &operator=(const T value)
{
static_assert(sizeof(m_value) >= sizeof(T));
MPT_ASSERT((intptr_t(&m_value) & 3) == 0);
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
InterlockedExchange(&m_value, static_cast<LONG>(value));
#else
_InterlockedExchange(&m_value, static_cast<LONG>(value));
#endif
return *this;
}
operator T() const
{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
return static_cast<T>(InterlockedAdd(&m_value, 0));
#else
return static_cast<T>(_InterlockedExchangeAdd(&m_value, 0));
#endif
}
T exchange(T desired)
{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
return static_cast<T>(InterlockedExchange(&m_value, static_cast<LONG>(desired)));
#else
return static_cast<T>(_InterlockedExchange(&m_value, static_cast<LONG>(desired)));
#endif
}
T fetch_add(T arg)
{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
return static_cast<T>(InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg)));
#else
return static_cast<T>(_InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg)));
#endif
}
bool compare_exchange_strong(T &expected, T desired)
{
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
return InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected);
#else
return _InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected);
#endif
}
private:
mutable LONG m_value;
};
// Host-to-bridge parameter automation message
struct AutomationQueue
{
struct Parameter
{
uint32 index;
float value;
};
BridgeAtomic<int32> pendingEvents; // Number of pending automation events
Parameter params[64]; // Automation events
};
// Host-to-bridge message to initiate a process call.
struct ProcessMsg
{
enum ProcessType : int32
{
process = 0,
processReplacing,
processDoubleReplacing,
};
int32 processType;
int32 numInputs;
int32 numOutputs;
int32 sampleFrames;
// Input and output buffers follow
};
// General message header
struct MsgHeader
{
enum BridgeMessageType : uint32
{
// Management messages, host to bridge
newInstance,
init,
// Management messages, bridge to host
errorMsg,
exceptionMsg,
// VST messages, common
dispatch,
// VST messages, host to bridge
setParameter,
getParameter,
automate,
};
BridgeAtomic<bool> isUsed;
uint32 size; // Size of complete message, including this header
uint32 type; // See BridgeMessageType
};
// Host-to-bridge new instance message
struct NewInstanceMsg : public MsgHeader
{
wchar_t memName[64]; // Shared memory object name;
};
// Host-to-bridge initialization message
struct InitMsg : public MsgHeader
{
int32 result;
int32 hostPtrSize; // Size of VstIntPtr in host
uint32 mixBufSize; // Interal mix buffer size (for shared memory audio buffers)
int32 pluginID; // ID to use when sending messages to host
uint32 fullMemDump; // When crashing, create full memory dumps instead of stack dumps
wchar_t str[_MAX_PATH]; // Plugin file to load. Out: Error message if result != 0.
};
// Host-to-bridge or bridge-to-host VST dispatch message
struct DispatchMsg : public MsgHeader
{
int32 opcode;
int32 index;
int64 value;
int64 ptr; // Usually, this will be the size of whatever ptr points to. In that case, the data itself is stored after this struct.
float opt;
int32 result;
};
// Host-to-bridge VST setParameter / getParameter message
struct ParameterMsg : public MsgHeader
{
int32 index; // Parameter ID
float value; // Parameter value (in/out)
};
// Bridge-to-host error message
struct ErrorMsg : public MsgHeader
{
wchar_t str[64];
};
// Universal bridge message format
union BridgeMessage
{
MsgHeader header;
NewInstanceMsg newInstance;
InitMsg init;
DispatchMsg dispatch;
ParameterMsg parameter;
ErrorMsg error;
uint8 dummy[2048]; // Enough space for most default structs, e.g. 2x speaker negotiation struct
void SetType(MsgHeader::BridgeMessageType msgType, uint32 size)
{
header.isUsed = true;
header.size = size;
header.type = msgType;
}
void NewInstance(const wchar_t *memName)
{
SetType(MsgHeader::newInstance, sizeof(NewInstanceMsg));
wcsncpy(newInstance.memName, memName, std::size(newInstance.memName) - 1);
}
void Init(const wchar_t *pluginPath, uint32 mixBufSize, int32 pluginID, bool fullMemDump)
{
SetType(MsgHeader::init, sizeof(InitMsg));
init.result = 0;
init.hostPtrSize = sizeof(intptr_t);
init.mixBufSize = mixBufSize;
init.pluginID = pluginID;
init.fullMemDump = fullMemDump;
wcsncpy(init.str, pluginPath, std::size(init.str) - 1);
}
void Dispatch(int32 opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
{
SetType(MsgHeader::dispatch, sizeof(DispatchMsg) + extraDataSize);
dispatch.result = 0;
dispatch.opcode = opcode;
dispatch.index = index;
dispatch.value = value;
dispatch.ptr = ptr;
dispatch.opt = opt;
}
void Dispatch(Vst::VstOpcodeToHost opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
{
Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize);
}
void Dispatch(Vst::VstOpcodeToPlugin opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
{
Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize);
}
void SetParameter(int32 index, float value)
{
SetType(MsgHeader::setParameter, sizeof(ParameterMsg));
parameter.index = index;
parameter.value = value;
}
void GetParameter(int32 index)
{
SetType(MsgHeader::getParameter, sizeof(ParameterMsg));
parameter.index = index;
parameter.value = 0.0f;
}
void Automate()
{
// Dummy message
SetType(MsgHeader::automate, sizeof(MsgHeader));
}
void Error(const wchar_t *text)
{
SetType(MsgHeader::errorMsg, sizeof(ErrorMsg));
wcsncpy(error.str, text, std::size(error.str) - 1);
error.str[std::size(error.str) - 1] = 0;
}
// Copy message to target and clear delivery status
void CopyTo(BridgeMessage &target)
{
std::memcpy(&target, this, std::min(static_cast<size_t>(header.size), sizeof(BridgeMessage)));
header.isUsed = false;
}
};
// This is the maximum size of dispatch data that can be sent in a message. If you want to dispatch more data, use a secondary medium for sending it along.
inline constexpr size_t DISPATCH_DATA_SIZE = (sizeof(BridgeMessage) - sizeof(DispatchMsg));
static_assert(DISPATCH_DATA_SIZE >= 256, "There should be room for at least 256 bytes of dispatch data!");
// The array size should be more than enough for any realistic scenario with nested and simultaneous dispatch calls
inline constexpr int MSG_STACK_SIZE = 16;
using MsgStack = std::array<BridgeMessage, MSG_STACK_SIZE>;
// Ensuring that our HWND looks the same to both 32-bit and 64-bit processes
struct BridgeHWND
{
public:
void operator=(HWND handle) { m_handle = static_cast<int32>(reinterpret_cast<intptr_t>(handle)); }
operator HWND() const { return reinterpret_cast<HWND>(static_cast<intptr_t>(m_handle)); }
protected:
BridgeAtomic<int32> m_handle;
};
// Layout of the shared memory chunk
struct SharedMemLayout
{
union
{
Vst::AEffect effect; // Native layout from host perspective
AEffect32 effect32;
AEffect64 effect64;
};
MsgStack ipcMessages;
AutomationQueue automationQueue;
Vst::VstTimeInfo timeInfo;
BridgeHWND hostCommWindow;
BridgeHWND bridgeCommWindow;
int32 bridgePluginID;
BridgeAtomic<int32> tailSize;
BridgeAtomic<int32> audioThreadToHostMsgID;
BridgeAtomic<int32> audioThreadToBridgeMsgID;
};
static_assert(sizeof(Vst::AEffect) <= sizeof(AEffect64), "Something's going very wrong here.");
// For caching parameter information
struct ParameterInfo
{
Vst::VstParameterProperties props;
char name[64];
char label[64];
char display[64];
};
#pragma pack(pop)
// Common variables that we will find in both the host and plugin side of the bridge (this is not shared memory)
class BridgeCommon
{
public:
BridgeCommon()
{
m_instanceCount++;
}
~BridgeCommon()
{
m_instanceCount--;
}
protected:
enum WindowMessage : UINT
{
WM_BRIDGE_KEYFIRST = WM_USER + 4000, // Must be consistent with VSTEditor.cpp!
WM_BRIDGE_KEYLAST = WM_BRIDGE_KEYFIRST + WM_KEYLAST - WM_KEYFIRST,
WM_BRIDGE_MESSAGE_TO_BRIDGE,
WM_BRIDGE_MESSAGE_TO_HOST,
WM_BRIDGE_DELETE_PLUGIN,
WM_BRIDGE_SUCCESS = 1337,
};
static std::vector<BridgeCommon *> m_plugins;
static HWND m_communicationWindow;
static int m_instanceCount;
static thread_local bool m_isAudioThread;
// Signals for host <-> bridge communication
Signal m_sigToHostAudio, m_sigToBridgeAudio;
Signal m_sigProcessAudio;
Event m_sigBridgeReady;
Event m_otherProcess; // Handle of "other" process (host handle in the bridge and vice versa)
// Shared memory segments
MappedMemory m_queueMem; // AEffect, message, some fixed size VST structures
MappedMemory m_processMem; // Process message + sample buffer
MappedMemory m_getChunkMem; // effGetChunk temporary memory
MappedMemory m_eventMem; // VstEvents memory
// Pointer into shared memory
SharedMemLayout /*volatile*/ *m_sharedMem = nullptr;
// Pointer size of the "other" side of the bridge, in bytes
int32 m_otherPtrSize = 0;
int32 m_thisPluginID = 0;
int32 m_otherPluginID = 0;
static void CreateCommunicationWindow(WNDPROC windowProc)
{
static constexpr TCHAR windowClassName[] = _T("OpenMPTPluginBridgeCommunication");
static bool registered = false;
if(!registered)
{
registered = true;
WNDCLASSEX wndClass;
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = windowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = GetModuleHandle(nullptr);
wndClass.hIcon = nullptr;
wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndClass.hbrBackground = nullptr;
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = windowClassName;
wndClass.hIconSm = nullptr;
RegisterClassEx(&wndClass);
}
m_communicationWindow = CreateWindow(
windowClassName,
_T("OpenMPT Plugin Bridge Communication"),
WS_POPUP,
CW_USEDEFAULT,
CW_USEDEFAULT,
1,
1,
HWND_MESSAGE,
nullptr,
GetModuleHandle(nullptr),
nullptr);
}
bool CreateSignals(const wchar_t *mapName)
{
wchar_t readyName[64];
wcscpy(readyName, mapName);
wcscat(readyName, L"rdy");
return m_sigToHostAudio.Create(mapName, L"sha")
&& m_sigToBridgeAudio.Create(mapName, L"sba")
&& m_sigProcessAudio.Create(mapName, L"prc")
&& m_sigBridgeReady.Create(false, readyName);
}
// Copy a message to shared memory and return relative position.
int CopyToSharedMemory(const BridgeMessage &msg, MsgStack &stack)
{
MPT_ASSERT(msg.header.isUsed);
for(int i = 0; i < MSG_STACK_SIZE; i++)
{
BridgeMessage &targetMsg = stack[i];
bool expected = false;
if(targetMsg.header.isUsed.compare_exchange_strong(expected, true))
{
std::memcpy(&targetMsg, &msg, std::min(sizeof(BridgeMessage), size_t(msg.header.size)));
return i;
}
}
return -1;
}
};
OPENMPT_NAMESPACE_END