362 lines
13 KiB
C++
362 lines
13 KiB
C++
/* -LICENSE-START-
|
|
** Copyright (c) 2012 Blackmagic Design
|
|
**
|
|
** Permission is hereby granted, free of charge, to any person or organization
|
|
** obtaining a copy of the software and accompanying documentation (the
|
|
** "Software") to use, reproduce, display, distribute, sub-license, execute,
|
|
** and transmit the Software, and to prepare derivative works of the Software,
|
|
** and to permit third-parties to whom the Software is furnished to do so, in
|
|
** accordance with:
|
|
**
|
|
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
** Agreement for the Software Development Kit ("EULA") available at
|
|
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
**
|
|
** (2) if the Software is obtained from any third party, such licensing terms
|
|
** as notified by that third party,
|
|
**
|
|
** and all subject to the following:
|
|
**
|
|
** (3) the copyright notices in the Software and this entire statement,
|
|
** including the above license grant, this restriction and the following
|
|
** disclaimer, must be included in all copies of the Software, in whole or in
|
|
** part, and all derivative works of the Software, unless such copies or
|
|
** derivative works are solely in the form of machine-executable object code
|
|
** generated by a source language processor.
|
|
**
|
|
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
** DEALINGS IN THE SOFTWARE.
|
|
**
|
|
** A copy of the Software is available free of charge at
|
|
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
**
|
|
** -LICENSE-END-
|
|
*/
|
|
|
|
#ifndef __OPENGL_COMPOSITE_H__
|
|
#define __OPENGL_COMPOSITE_H__
|
|
|
|
#include <windows.h>
|
|
#include <process.h>
|
|
#include <tchar.h>
|
|
#include <gl/gl.h>
|
|
#include <gl/glu.h>
|
|
|
|
#include <objbase.h>
|
|
#include <atlbase.h>
|
|
#include <comutil.h>
|
|
#include "DeckLinkAPI_h.h"
|
|
|
|
#include "VideoFrameTransfer.h"
|
|
#include "RuntimeHost.h"
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <deque>
|
|
|
|
class PlayoutDelegate;
|
|
class CaptureDelegate;
|
|
class PinnedMemoryAllocator;
|
|
class ControlServer;
|
|
class OscServer;
|
|
|
|
|
|
class OpenGLComposite
|
|
{
|
|
public:
|
|
OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC);
|
|
~OpenGLComposite();
|
|
|
|
bool InitDeckLink();
|
|
bool Start();
|
|
bool Stop();
|
|
bool ReloadShader();
|
|
std::string GetRuntimeStateJson() const;
|
|
bool AddLayer(const std::string& shaderId, std::string& error);
|
|
bool RemoveLayer(const std::string& layerId, std::string& error);
|
|
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
|
|
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
|
|
bool SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error);
|
|
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
|
|
bool UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error);
|
|
bool UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
|
|
bool ResetLayerParameters(const std::string& layerId, std::string& error);
|
|
bool SaveStackPreset(const std::string& presetName, std::string& error);
|
|
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
|
|
|
void resizeGL(WORD width, WORD height);
|
|
void paintGL();
|
|
|
|
void VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
|
void PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result);
|
|
|
|
private:
|
|
void resizeWindow(int width, int height);
|
|
bool CheckOpenGLExtensions();
|
|
|
|
CaptureDelegate* mCaptureDelegate;
|
|
PlayoutDelegate* mPlayoutDelegate;
|
|
HWND hGLWnd;
|
|
HDC hGLDC;
|
|
HGLRC hGLRC;
|
|
CRITICAL_SECTION pMutex;
|
|
|
|
// DeckLink
|
|
IDeckLinkInput* mDLInput;
|
|
IDeckLinkOutput* mDLOutput;
|
|
IDeckLinkKeyer* mDLKeyer;
|
|
std::deque<IDeckLinkMutableVideoFrame*> mDLOutputVideoFrameQueue;
|
|
PinnedMemoryAllocator* mPlayoutAllocator;
|
|
BMDTimeValue mFrameDuration;
|
|
BMDTimeScale mFrameTimescale;
|
|
unsigned mTotalPlayoutFrames;
|
|
unsigned mInputFrameWidth;
|
|
unsigned mInputFrameHeight;
|
|
unsigned mOutputFrameWidth;
|
|
unsigned mOutputFrameHeight;
|
|
std::string mInputDisplayModeName;
|
|
std::string mOutputDisplayModeName;
|
|
bool mHasNoInputSource;
|
|
std::string mDeckLinkOutputModelName;
|
|
bool mDeckLinkSupportsInternalKeying;
|
|
bool mDeckLinkSupportsExternalKeying;
|
|
bool mDeckLinkKeyerInterfaceAvailable;
|
|
bool mDeckLinkExternalKeyingActive;
|
|
std::string mDeckLinkStatusMessage;
|
|
|
|
// OpenGL data
|
|
bool mFastTransferExtensionAvailable;
|
|
GLuint mCaptureTexture;
|
|
GLuint mDecodedTexture;
|
|
GLuint mLayerTempTexture;
|
|
GLuint mFBOTexture;
|
|
GLuint mOutputTexture;
|
|
GLuint mUnpinnedTextureBuffer;
|
|
GLuint mDecodeFrameBuf;
|
|
GLuint mLayerTempFrameBuf;
|
|
GLuint mIdFrameBuf;
|
|
GLuint mOutputFrameBuf;
|
|
GLuint mIdColorBuf;
|
|
GLuint mIdDepthBuf;
|
|
GLuint mFullscreenVAO;
|
|
GLuint mGlobalParamsUBO;
|
|
GLuint mDecodeProgram;
|
|
GLuint mDecodeVertexShader;
|
|
GLuint mDecodeFragmentShader;
|
|
GLsizeiptr mGlobalParamsUBOSize;
|
|
int mViewWidth;
|
|
int mViewHeight;
|
|
std::unique_ptr<RuntimeHost> mRuntimeHost;
|
|
std::unique_ptr<ControlServer> mControlServer;
|
|
std::unique_ptr<OscServer> mOscServer;
|
|
|
|
struct LayerProgram
|
|
{
|
|
struct TextureBinding
|
|
{
|
|
std::string samplerName;
|
|
std::filesystem::path sourcePath;
|
|
GLuint texture = 0;
|
|
};
|
|
|
|
std::string layerId;
|
|
std::string shaderId;
|
|
GLuint program = 0;
|
|
GLuint vertexShader = 0;
|
|
GLuint fragmentShader = 0;
|
|
std::vector<TextureBinding> textureBindings;
|
|
};
|
|
std::vector<LayerProgram> mLayerPrograms;
|
|
|
|
struct HistorySlot
|
|
{
|
|
GLuint texture = 0;
|
|
GLuint framebuffer = 0;
|
|
};
|
|
|
|
struct HistoryRing
|
|
{
|
|
std::vector<HistorySlot> slots;
|
|
std::size_t nextWriteIndex = 0;
|
|
std::size_t filledCount = 0;
|
|
unsigned effectiveLength = 0;
|
|
TemporalHistorySource historySource = TemporalHistorySource::None;
|
|
};
|
|
|
|
HistoryRing mSourceHistoryRing;
|
|
std::map<std::string, HistoryRing> mPreLayerHistoryByLayerId;
|
|
bool mTemporalHistoryNeedsReset;
|
|
|
|
bool InitOpenGLState();
|
|
bool compileLayerPrograms(int errorMessageSize, char* errorMessage);
|
|
bool compileSingleLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
|
bool compileDecodeShader(int errorMessageSize, char* errorMessage);
|
|
void destroyLayerPrograms();
|
|
void destroySingleLayerProgram(LayerProgram& layerProgram);
|
|
void destroyDecodeShaderProgram();
|
|
void renderDecodePass();
|
|
void renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state);
|
|
bool loadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
|
void bindLayerTextureAssets(const LayerProgram& layerProgram);
|
|
void renderEffect();
|
|
bool PollRuntimeChanges();
|
|
void broadcastRuntimeState();
|
|
bool updateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
|
bool validateTemporalTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, std::string& error) const;
|
|
bool ensureTemporalHistoryResources(const std::vector<RuntimeRenderState>& layerStates, std::string& error);
|
|
bool createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error);
|
|
void destroyHistoryRing(HistoryRing& ring);
|
|
void destroyTemporalHistoryResources();
|
|
void resetTemporalHistoryState();
|
|
void pushFramebufferToHistoryRing(GLuint sourceFramebuffer, HistoryRing& ring);
|
|
void bindHistorySamplers(const RuntimeRenderState& state, GLuint currentSourceTexture);
|
|
GLuint resolveHistoryTexture(const HistoryRing& ring, GLuint fallbackTexture, std::size_t framesAgo) const;
|
|
unsigned sourceHistoryAvailableCount() const;
|
|
unsigned temporalHistoryAvailableCountForLayer(const std::string& layerId) const;
|
|
};
|
|
|
|
////////////////////////////////////////////
|
|
// PinnedMemoryAllocator
|
|
////////////////////////////////////////////
|
|
class PinnedMemoryAllocator : public IDeckLinkVideoBufferAllocator
|
|
{
|
|
public:
|
|
PinnedMemoryAllocator(HDC hdc, HGLRC hglrc, VideoFrameTransfer::Direction direction, unsigned cacheSize, unsigned bufferSize);
|
|
virtual ~PinnedMemoryAllocator();
|
|
|
|
bool transferFrame(void* address, GLuint gpuTexture);
|
|
void waitForTransferComplete(void* address);
|
|
unsigned bufferSize() { return mBufferSize; }
|
|
|
|
// IUnknown methods
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) override;
|
|
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
|
|
virtual ULONG STDMETHODCALLTYPE Release(void) override;
|
|
|
|
// IDeckLinkVideoBufferAllocator methods
|
|
virtual HRESULT STDMETHODCALLTYPE AllocateVideoBuffer (IDeckLinkVideoBuffer** allocatedBuffer) override;
|
|
|
|
private:
|
|
|
|
void unPinAddress(void* address);
|
|
|
|
private:
|
|
HDC mHGLDC;
|
|
HGLRC mHGLRC;
|
|
std::atomic<ULONG> mRefCount;
|
|
VideoFrameTransfer::Direction mDirection;
|
|
std::map<void*, VideoFrameTransfer*> mFrameTransfer;
|
|
unsigned mBufferSize;
|
|
std::vector<void*> mFrameCache;
|
|
unsigned mFrameCacheSize;
|
|
};
|
|
|
|
////////////////////////////////////////////
|
|
// InputAllocatorPool
|
|
////////////////////////////////////////////
|
|
|
|
class InputAllocatorPool : public IDeckLinkVideoBufferAllocatorProvider
|
|
{
|
|
public:
|
|
InputAllocatorPool(HDC hdc, HGLRC hglrc);
|
|
|
|
// IUnknown interface
|
|
ULONG STDMETHODCALLTYPE AddRef() override;
|
|
ULONG STDMETHODCALLTYPE Release() override;
|
|
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppv) override;
|
|
|
|
// IDeckLinkVideoBufferAllocatorProvider interface
|
|
HRESULT STDMETHODCALLTYPE GetVideoBufferAllocator(
|
|
/* [in] */ unsigned int bufferSize,
|
|
/* [in] */ unsigned int width,
|
|
/* [in] */ unsigned int height,
|
|
/* [in] */ unsigned int rowBytes,
|
|
/* [in] */ BMDPixelFormat pixelFormat,
|
|
/* [out] */ IDeckLinkVideoBufferAllocator **allocator) override;
|
|
|
|
private:
|
|
std::atomic<ULONG> mRefCount;
|
|
std::map<unsigned int, CComPtr<PinnedMemoryAllocator> > mAllocatorBySize;
|
|
HDC mHDC;
|
|
HGLRC mHGLRC;
|
|
};
|
|
|
|
////////////////////////////////////////////
|
|
// DeckLinkVideoBuffer
|
|
////////////////////////////////////////////
|
|
class DeckLinkVideoBuffer : public IDeckLinkVideoBuffer
|
|
{
|
|
public:
|
|
explicit DeckLinkVideoBuffer(std::shared_ptr<void>& buffer, PinnedMemoryAllocator* parent);
|
|
virtual ~DeckLinkVideoBuffer() = default;
|
|
|
|
// IUnknown interface
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override;
|
|
virtual ULONG STDMETHODCALLTYPE AddRef(void) override;
|
|
virtual ULONG STDMETHODCALLTYPE Release(void) override;
|
|
|
|
// IDeckLinkVideoBuffer interface
|
|
virtual HRESULT STDMETHODCALLTYPE GetBytes(void** buffer) override;
|
|
virtual HRESULT STDMETHODCALLTYPE GetSize(uint64_t* size) override;
|
|
virtual HRESULT STDMETHODCALLTYPE StartAccess(BMDBufferAccessFlags flags) override;
|
|
virtual HRESULT STDMETHODCALLTYPE EndAccess(BMDBufferAccessFlags flags) override;
|
|
|
|
private:
|
|
CComPtr<PinnedMemoryAllocator> mParentAllocator; // Dual-purpose: allocator owns mem this points to, and to access transferFrame() via a QueryInterface
|
|
std::atomic<ULONG> mRefCount;
|
|
std::shared_ptr<void> mBuffer;
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////
|
|
// Capture Delegate Class
|
|
////////////////////////////////////////////
|
|
|
|
class CaptureDelegate : public IDeckLinkInputCallback
|
|
{
|
|
OpenGLComposite* m_pOwner;
|
|
LONG mRefCount;
|
|
|
|
public:
|
|
CaptureDelegate (OpenGLComposite* pOwner);
|
|
|
|
// IUnknown needs only a dummy implementation
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv);
|
|
virtual ULONG STDMETHODCALLTYPE AddRef ();
|
|
virtual ULONG STDMETHODCALLTYPE Release ();
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived (IDeckLinkVideoInputFrame *videoFrame, IDeckLinkAudioInputPacket *audioPacket);
|
|
virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged (BMDVideoInputFormatChangedEvents notificationEvents, IDeckLinkDisplayMode *newDisplayMode, BMDDetectedVideoInputFormatFlags detectedSignalFlags);
|
|
};
|
|
|
|
////////////////////////////////////////////
|
|
// Render Delegate Class
|
|
////////////////////////////////////////////
|
|
|
|
class PlayoutDelegate : public IDeckLinkVideoOutputCallback
|
|
{
|
|
OpenGLComposite* m_pOwner;
|
|
LONG mRefCount;
|
|
|
|
public:
|
|
PlayoutDelegate (OpenGLComposite* pOwner);
|
|
|
|
// IUnknown needs only a dummy implementation
|
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv);
|
|
virtual ULONG STDMETHODCALLTYPE AddRef ();
|
|
virtual ULONG STDMETHODCALLTYPE Release ();
|
|
|
|
virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result);
|
|
virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped ();
|
|
};
|
|
|
|
#endif // __OPENGL_COMPOSITE_H__
|