Files
video-shader-toys/apps/LoopThroughWithOpenGLCompositing/LoopThroughWithOpenGLCompositing.cpp
Aiden ef829bf3ef
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 1m32s
CI / Windows Release Package (push) Successful in 2m20s
Added control interface
2026-05-06 12:55:36 +10:00

549 lines
16 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-
*/
//
// LoopThroughWithOpenGLCompositing.cpp
// LoopThroughWithOpenGLCompositing
//
#include "stdafx.h"
#include "resource.h"
#include "OpenGLComposite.h"
#include <algorithm>
#include <shellapi.h>
#include <string>
#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#endif
#ifndef WGL_CONTEXT_MINOR_VERSION_ARB
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#endif
#ifndef WGL_CONTEXT_PROFILE_MASK_ARB
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#endif
#ifndef WGL_CONTEXT_CORE_PROFILE_BIT_ARB
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#endif
#define MAX_LOADSTRING 100
// Declaration for Window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hShareContext, const int* attribList);
namespace
{
const int kStatusStripHeight = 92;
const int kStatusPadding = 8;
const int kStatusLabelWidth = 58;
const int kStatusButtonWidth = 86;
const int kStatusRowHeight = 24;
const int kStatusGap = 6;
const UINT kCreateStatusStripMessage = WM_APP + 1;
enum StatusControlId
{
kControlUrlEditId = 2001,
kDocsUrlEditId = 2002,
kOscAddressEditId = 2003,
kOpenControlButtonId = 2004,
kOpenDocsButtonId = 2005
};
struct StatusStripControls
{
HWND panel = NULL;
HWND controlLabel = NULL;
HWND controlUrl = NULL;
HWND openControl = NULL;
HWND docsLabel = NULL;
HWND docsUrl = NULL;
HWND openDocs = NULL;
HWND oscLabel = NULL;
HWND oscAddress = NULL;
};
bool StatusStripCreated(const StatusStripControls& controls)
{
return controls.panel != NULL;
}
HWND CreateStatusChild(HWND parent, const char* className, const char* text, DWORD style, int controlId)
{
return CreateWindowExA(
0,
className,
text,
WS_CHILD | WS_VISIBLE | style,
0,
0,
0,
0,
parent,
reinterpret_cast<HMENU>(static_cast<INT_PTR>(controlId)),
reinterpret_cast<HINSTANCE>(GetWindowLongPtr(parent, GWLP_HINSTANCE)),
NULL);
}
void CreateStatusStrip(HWND hWnd, StatusStripControls& controls)
{
controls.panel = CreateStatusChild(hWnd, "STATIC", "", SS_NOTIFY, 0);
controls.controlLabel = CreateStatusChild(hWnd, "STATIC", "Control", SS_LEFT, 0);
controls.controlUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY, kControlUrlEditId);
controls.openControl = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON, kOpenControlButtonId);
controls.docsLabel = CreateStatusChild(hWnd, "STATIC", "Docs", SS_LEFT, 0);
controls.docsUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY, kDocsUrlEditId);
controls.openDocs = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON, kOpenDocsButtonId);
controls.oscLabel = CreateStatusChild(hWnd, "STATIC", "OSC", SS_LEFT, 0);
controls.oscAddress = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY, kOscAddressEditId);
HFONT guiFont = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
HWND children[] = {
controls.controlLabel,
controls.controlUrl,
controls.openControl,
controls.docsLabel,
controls.docsUrl,
controls.openDocs,
controls.oscLabel,
controls.oscAddress
};
for (HWND child : children)
{
if (child)
SendMessage(child, WM_SETFONT, reinterpret_cast<WPARAM>(guiFont), TRUE);
}
SetWindowTextA(controls.controlUrl, "Starting control server...");
SetWindowTextA(controls.docsUrl, "Starting API docs...");
SetWindowTextA(controls.oscAddress, "Starting OSC listener...");
}
void LayoutStatusStrip(HWND hWnd, const StatusStripControls& controls)
{
RECT clientRect = {};
if (!GetClientRect(hWnd, &clientRect) || !controls.panel)
return;
const int clientWidth = static_cast<int>(clientRect.right - clientRect.left);
const int clientHeight = static_cast<int>(clientRect.bottom - clientRect.top);
const int panelTop = std::max(0, clientHeight - kStatusStripHeight);
MoveWindow(controls.panel, 0, panelTop, clientWidth, kStatusStripHeight, TRUE);
const int rowX = kStatusPadding;
const int editX = rowX + kStatusLabelWidth + kStatusGap;
const int buttonX = std::max(editX, clientWidth - kStatusPadding - kStatusButtonWidth);
const int editWidth = std::max(80, buttonX - editX - kStatusGap);
const int oscWidth = std::max(80, clientWidth - editX - kStatusPadding);
const int row1 = panelTop + kStatusPadding;
const int row2 = row1 + kStatusRowHeight + kStatusGap;
const int row3 = row2 + kStatusRowHeight + kStatusGap;
MoveWindow(controls.controlLabel, rowX, row1 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.controlUrl, editX, row1, editWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.openControl, buttonX, row1, kStatusButtonWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.docsLabel, rowX, row2 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.docsUrl, editX, row2, editWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.openDocs, buttonX, row2, kStatusButtonWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.oscLabel, rowX, row3 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
MoveWindow(controls.oscAddress, editX, row3, oscWidth, kStatusRowHeight, TRUE);
}
void UpdateStatusStrip(const StatusStripControls& controls, const OpenGLComposite& composite)
{
if (!StatusStripCreated(controls))
return;
SetWindowTextA(controls.controlUrl, composite.GetControlUrl().c_str());
SetWindowTextA(controls.docsUrl, composite.GetDocsUrl().c_str());
SetWindowTextA(controls.oscAddress, composite.GetOscAddress().c_str());
}
void OpenUrl(const char* url)
{
ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
}
}
void ShowUnhandledExceptionMessage(const char* prefix)
{
try
{
throw;
}
catch (const std::exception& exception)
{
std::string message = std::string(prefix) + "\n\n" + exception.what();
MessageBoxA(NULL, message.c_str(), "Unhandled exception", MB_OK | MB_ICONERROR);
}
catch (...)
{
MessageBoxA(NULL, prefix, "Unhandled exception", MB_OK | MB_ICONERROR);
}
}
// Select the pixel format for a given device context
void SetDCPixelFormat(HDC hDC)
{
int nPixelFormat;
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size of this structure
1, // Version of this structure
PFD_DRAW_TO_WINDOW | // Draw to Window (not to bitmap)
PFD_SUPPORT_OPENGL | // Support OpenGL calls in window
PFD_DOUBLEBUFFER, // Double buffered mode
PFD_TYPE_RGBA, // RGBA Color mode
32, // Want 32 bit color
0,0,0,0,0,0, // Not used to select mode
0,0, // Not used to select mode
0,0,0,0,0, // Not used to select mode
16, // Size of depth buffer
0, // Not used
0, // Not used
0, // Not used
0, // Not used
0,0,0 }; // Not used
// Choose a pixel format that best matches that described in pfd
nPixelFormat = ChoosePixelFormat(hDC, &pfd);
// Set the pixel format for the device context
SetPixelFormat(hDC, nPixelFormat, &pfd);
}
HGLRC CreateModernOpenGLContext(HDC hDC)
{
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB =
reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(wglGetProcAddress("wglCreateContextAttribsARB"));
if (!wglCreateContextAttribsARB)
return NULL;
const int versionCandidates[][2] =
{
{ 4, 5 },
{ 4, 3 },
{ 3, 3 }
};
for (const auto& version : versionCandidates)
{
const int attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, version[0],
WGL_CONTEXT_MINOR_VERSION_ARB, version[1],
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
HGLRC modernContext = wglCreateContextAttribsARB(hDC, 0, attribs);
if (modernContext != NULL)
return modernContext;
}
return NULL;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg; // Windows message structure
WNDCLASS wc; // Windows class structure
HWND hWnd; // Storeage for window handle
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_OPENGLOUTPUT, szWindowClass, MAX_LOADSTRING);
// Register Window style
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
// No need for background brush for OpenGL window
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = szWindowClass;
// Register the window class
if (RegisterClass(&wc) == 0)
return FALSE;
// Create the main application window
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
CW_USEDEFAULT, 0, 250, 250, NULL, NULL, hInstance, NULL);
// If window was not created, quit
if (hWnd == NULL)
return FALSE;
// Display the window
ShowWindow(hWnd,SW_SHOW);
UpdateWindow(hWnd);
// Process application messages until the application closes
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// Window procedure, handles all messages for this program
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HGLRC hRC = NULL; // Permenant Rendering context
static HDC hDC = NULL; // Private GDI Device context
static OpenGLComposite* pOpenGLComposite = NULL;
static bool sInteractiveResize = false;
static StatusStripControls sStatusStrip;
switch (message)
{
// Window creation, setup for OpenGL context
case WM_CREATE:
{
try
{
// Store the device context
hDC = GetDC(hWnd);
// Select the pixel format
SetDCPixelFormat(hDC);
// Create the rendering context and make it current
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
HGLRC modernRC = CreateModernOpenGLContext(hDC);
if (modernRC == NULL)
{
MessageBox(NULL, _T("This application requires an OpenGL 3.3+ core profile context."), _T("OpenGL initialization Error."), MB_OK);
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
hRC = modernRC;
wglMakeCurrent(hDC, hRC);
// Initialize COM
HRESULT result;
result = CoInitialize(NULL);
if (FAILED(result))
{
MessageBox(NULL, _T("Initialization of COM failed."), _T("Application initialization Error."),MB_OK);
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
// Setup OpenGL and DeckLink capture and playout object
pOpenGLComposite = new OpenGLComposite(hWnd, hDC, hRC);
if (pOpenGLComposite->InitDeckLink())
{
wglMakeCurrent( NULL, NULL );
if (pOpenGLComposite->Start())
{
PostMessage(hWnd, kCreateStatusStripMessage, 0, 0);
break; // success
}
MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
}
else
{
MessageBoxA(NULL, "The OpenGL/DeckLink runtime failed to initialize. See the previous initialization message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
}
// Failed to initialize - cleanup
delete pOpenGLComposite;
pOpenGLComposite = NULL;
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
catch (...)
{
ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/DeckLink runtime.");
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
}
case kCreateStatusStripMessage:
if (pOpenGLComposite)
{
if (!StatusStripCreated(sStatusStrip))
CreateStatusStrip(hWnd, sStatusStrip);
UpdateStatusStrip(sStatusStrip, *pOpenGLComposite);
LayoutStatusStrip(hWnd, sStatusStrip);
InvalidateRect(hWnd, NULL, FALSE);
}
break;
case WM_DESTROY:
try
{
if (pOpenGLComposite)
{
pOpenGLComposite->Stop();
delete pOpenGLComposite;
}
}
catch (...)
{
ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/DeckLink runtime.");
}
// Deselect the current rendering context and delete it
wglMakeCurrent(hDC, NULL);
wglDeleteContext(hRC);
// Tell the application to terminate after the window is gone
PostQuitMessage(0);
break;
case WM_ENTERSIZEMOVE:
sInteractiveResize = true;
break;
case WM_EXITSIZEMOVE:
sInteractiveResize = false;
if (pOpenGLComposite)
{
RECT clientRect = {};
if (GetClientRect(hWnd, &clientRect))
pOpenGLComposite->resizeGL(static_cast<WORD>(clientRect.right - clientRect.left), static_cast<WORD>(clientRect.bottom - clientRect.top));
}
InvalidateRect(hWnd, NULL, FALSE);
break;
case WM_SIZE:
try
{
if (StatusStripCreated(sStatusStrip))
LayoutStatusStrip(hWnd, sStatusStrip);
if (pOpenGLComposite)
pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
}
catch (...)
{
ShowUnhandledExceptionMessage("Resize failed inside the OpenGL runtime.");
}
break;
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
try
{
PAINTSTRUCT paint = {};
BeginPaint(hWnd, &paint);
EndPaint(hWnd, &paint);
if (!sInteractiveResize && pOpenGLComposite)
{
wglMakeCurrent(hDC, hRC);
pOpenGLComposite->paintGL();
wglMakeCurrent( NULL, NULL );
}
}
catch (...)
{
wglMakeCurrent( NULL, NULL );
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
}
break;
case WM_KEYDOWN:
try
{
if (pOpenGLComposite && (wParam == 'R' || wParam == 'r'))
{
pOpenGLComposite->ReloadShader();
InvalidateRect(hWnd, NULL, FALSE);
}
}
catch (...)
{
ShowUnhandledExceptionMessage("Shader reload failed inside the OpenGL runtime.");
}
break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case kOpenControlButtonId:
if (pOpenGLComposite)
{
std::string url = pOpenGLComposite->GetControlUrl();
OpenUrl(url.c_str());
}
break;
case kOpenDocsButtonId:
if (pOpenGLComposite)
{
std::string url = pOpenGLComposite->GetDocsUrl();
OpenUrl(url.c_str());
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return (0L);
}