Folder fixes
This commit is contained in:
85
.vscode/launch.json
vendored
85
.vscode/launch.json
vendored
@@ -1,89 +1,6 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug LoopThroughWithOpenGLCompositing",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [
|
||||
{
|
||||
"name": "VST_DISABLE_INPUT_CAPTURE",
|
||||
"value": "1"
|
||||
}
|
||||
],
|
||||
"console": "internalConsole",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||
},
|
||||
{
|
||||
"name": "Debug LoopThroughWithOpenGLCompositing - sync readback experiment",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [
|
||||
{
|
||||
"name": "VST_OUTPUT_READBACK_MODE",
|
||||
"value": "sync"
|
||||
}
|
||||
],
|
||||
"console": "internalConsole",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||
},
|
||||
{
|
||||
"name": "Debug LoopThroughWithOpenGLCompositing - cached output experiment",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\LoopThroughWithOpenGLCompositing.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [
|
||||
{
|
||||
"name": "VST_OUTPUT_READBACK_MODE",
|
||||
"value": "cached_only"
|
||||
}
|
||||
],
|
||||
"console": "internalConsole",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build LoopThroughWithOpenGLCompositing Debug x64"
|
||||
},
|
||||
{
|
||||
"name": "Debug DeckLinkRenderCadenceProbe",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\DeckLinkRenderCadenceProbe.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"environment": [],
|
||||
"console": "externalTerminal",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"requireExactSource": true,
|
||||
"logging": {
|
||||
"moduleLoad": true
|
||||
},
|
||||
"preLaunchTask": "Build DeckLinkRenderCadenceProbe Debug x64"
|
||||
},
|
||||
{
|
||||
"name": "Debug RenderCadenceCompositor",
|
||||
"type": "cppvsdbg",
|
||||
@@ -91,7 +8,7 @@
|
||||
"program": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug\\RenderCadenceCompositor.exe",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"console": "externalTerminal",
|
||||
"symbolSearchPath": "${workspaceFolder}\\build\\vs2022-x64-debug\\Debug",
|
||||
|
||||
92
.vscode/tasks.json
vendored
92
.vscode/tasks.json
vendored
@@ -2,60 +2,19 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build LoopThroughWithOpenGLCompositing Debug x64",
|
||||
"label": "Configure Debug x64",
|
||||
"type": "process",
|
||||
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-debug",
|
||||
"--config",
|
||||
"Debug",
|
||||
"--target",
|
||||
"LoopThroughWithOpenGLCompositing",
|
||||
"--parallel"
|
||||
"--preset",
|
||||
"vs2022-x64-debug"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build LoopThroughWithOpenGLCompositing Release x64",
|
||||
"type": "process",
|
||||
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-release",
|
||||
"--config",
|
||||
"Release",
|
||||
"--target",
|
||||
"LoopThroughWithOpenGLCompositing",
|
||||
"--parallel"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build DeckLinkRenderCadenceProbe Debug x64",
|
||||
"type": "process",
|
||||
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-debug",
|
||||
"--config",
|
||||
"Debug",
|
||||
"--target",
|
||||
"DeckLinkRenderCadenceProbe",
|
||||
"--parallel"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build RenderCadenceCompositor Debug x64",
|
||||
"type": "process",
|
||||
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-debug",
|
||||
@@ -65,13 +24,50 @@
|
||||
"RenderCadenceCompositor",
|
||||
"--parallel"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"dependsOn": "Configure Debug x64",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build RenderCadenceCompositor Release x64",
|
||||
"type": "process",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-release",
|
||||
"--config",
|
||||
"Release",
|
||||
"--target",
|
||||
"RenderCadenceCompositor",
|
||||
"--parallel"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Clean LoopThroughWithOpenGLCompositing Debug x64",
|
||||
"label": "Run Native Tests Debug x64",
|
||||
"type": "process",
|
||||
"command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-debug",
|
||||
"--config",
|
||||
"Debug",
|
||||
"--target",
|
||||
"RUN_TESTS",
|
||||
"--parallel"
|
||||
],
|
||||
"group": "test",
|
||||
"dependsOn": "Configure Debug x64",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Clean Debug x64",
|
||||
"type": "process",
|
||||
"command": "cmake",
|
||||
"args": [
|
||||
"--build",
|
||||
"${workspaceFolder}\\build\\vs2022-x64-debug",
|
||||
|
||||
1204
CMakeLists.txt
1204
CMakeLists.txt
File diff suppressed because it is too large
Load Diff
42
src/platform/NativeHandles.h
Normal file
42
src/platform/NativeHandles.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
class UniqueHandle
|
||||
{
|
||||
public:
|
||||
explicit UniqueHandle(HANDLE handle = NULL) : mHandle(handle) {}
|
||||
~UniqueHandle() { reset(); }
|
||||
|
||||
UniqueHandle(const UniqueHandle&) = delete;
|
||||
UniqueHandle& operator=(const UniqueHandle&) = delete;
|
||||
|
||||
UniqueHandle(UniqueHandle&& other) noexcept : mHandle(other.release()) {}
|
||||
|
||||
UniqueHandle& operator=(UniqueHandle&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
reset(other.release());
|
||||
return *this;
|
||||
}
|
||||
|
||||
HANDLE get() const { return mHandle; }
|
||||
bool valid() const { return mHandle != NULL && mHandle != INVALID_HANDLE_VALUE; }
|
||||
|
||||
HANDLE release()
|
||||
{
|
||||
HANDLE handle = mHandle;
|
||||
mHandle = NULL;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void reset(HANDLE handle = NULL)
|
||||
{
|
||||
if (valid())
|
||||
CloseHandle(mHandle);
|
||||
mHandle = handle;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mHandle;
|
||||
};
|
||||
42
src/platform/NativeSockets.h
Normal file
42
src/platform/NativeSockets.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <winsock2.h>
|
||||
|
||||
class UniqueSocket
|
||||
{
|
||||
public:
|
||||
explicit UniqueSocket(SOCKET socket = INVALID_SOCKET) : mSocket(socket) {}
|
||||
~UniqueSocket() { reset(); }
|
||||
|
||||
UniqueSocket(const UniqueSocket&) = delete;
|
||||
UniqueSocket& operator=(const UniqueSocket&) = delete;
|
||||
|
||||
UniqueSocket(UniqueSocket&& other) noexcept : mSocket(other.release()) {}
|
||||
|
||||
UniqueSocket& operator=(UniqueSocket&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
reset(other.release());
|
||||
return *this;
|
||||
}
|
||||
|
||||
SOCKET get() const { return mSocket; }
|
||||
bool valid() const { return mSocket != INVALID_SOCKET; }
|
||||
|
||||
SOCKET release()
|
||||
{
|
||||
SOCKET socket = mSocket;
|
||||
mSocket = INVALID_SOCKET;
|
||||
return socket;
|
||||
}
|
||||
|
||||
void reset(SOCKET socket = INVALID_SOCKET)
|
||||
{
|
||||
if (valid())
|
||||
closesocket(mSocket);
|
||||
mSocket = socket;
|
||||
}
|
||||
|
||||
private:
|
||||
SOCKET mSocket;
|
||||
};
|
||||
212
src/render/GLExtensions.cpp
Normal file
212
src/render/GLExtensions.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
/* -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-
|
||||
*/
|
||||
//
|
||||
// GLExtensions.cpp
|
||||
// LoopThroughWithOpenGLCompositing
|
||||
//
|
||||
|
||||
#include "GLExtensions.h"
|
||||
|
||||
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
||||
PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
||||
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
||||
PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
||||
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
||||
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
||||
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
||||
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
||||
PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
||||
PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
||||
PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
||||
PFNGLFENCESYNCPROC glFenceSync;
|
||||
PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
|
||||
PFNGLDELETESYNCPROC glDeleteSync;
|
||||
PFNGLGENBUFFERSPROC glGenBuffers;
|
||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||
PFNGLBINDBUFFERPROC glBindBuffer;
|
||||
PFNGLBUFFERDATAPROC glBufferData;
|
||||
PFNGLMAPBUFFERPROC glMapBuffer;
|
||||
PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
||||
PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
||||
PFNGLACTIVETEXTUREPROC glActiveTexture;
|
||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
||||
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
||||
PFNGLCREATESHADERPROC glCreateShader;
|
||||
PFNGLDELETESHADERPROC glDeleteShader;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
||||
PFNGLSHADERSOURCEPROC glShaderSource;
|
||||
PFNGLCOMPILESHADERPROC glCompileShader;
|
||||
PFNGLGETSHADERIVPROC glGetShaderiv;
|
||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
||||
PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||
PFNGLATTACHSHADERPROC glAttachShader;
|
||||
PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||
PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
PFNGLUSEPROGRAMPROC glUseProgram;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
||||
PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
||||
PFNGLUNIFORM1IPROC glUniform1i;
|
||||
PFNGLUNIFORM1FPROC glUniform1f;
|
||||
PFNGLUNIFORM2FPROC glUniform2f;
|
||||
PFNGLUNIFORM4FPROC glUniform4f;
|
||||
|
||||
bool ResolveGLExtensions()
|
||||
{
|
||||
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) wglGetProcAddress("glGenFramebuffers");
|
||||
if (!glGenFramebuffers)
|
||||
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) wglGetProcAddress("glGenFramebuffersEXT");
|
||||
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) wglGetProcAddress("glGenRenderbuffers");
|
||||
if (!glGenRenderbuffers)
|
||||
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) wglGetProcAddress("glGenRenderbuffersEXT");
|
||||
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) wglGetProcAddress("glBindRenderbuffer");
|
||||
if (!glBindRenderbuffer)
|
||||
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) wglGetProcAddress("glBindRenderbufferEXT");
|
||||
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) wglGetProcAddress("glRenderbufferStorage");
|
||||
if (!glRenderbufferStorage)
|
||||
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) wglGetProcAddress("glRenderbufferStorageEXT");
|
||||
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) wglGetProcAddress("glDeleteFramebuffers");
|
||||
if (!glDeleteFramebuffers)
|
||||
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) wglGetProcAddress("glDeleteFramebuffersEXT");
|
||||
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) wglGetProcAddress("glDeleteRenderbuffers");
|
||||
if (!glDeleteRenderbuffers)
|
||||
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) wglGetProcAddress("glDeleteRenderbuffersEXT");
|
||||
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) wglGetProcAddress("glBindFramebuffer");
|
||||
if (!glBindFramebuffer)
|
||||
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) wglGetProcAddress("glBindFramebufferEXT");
|
||||
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) wglGetProcAddress("glFramebufferTexture2D");
|
||||
if (!glFramebufferTexture2D)
|
||||
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) wglGetProcAddress("glFramebufferTexture2DEXT");
|
||||
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) wglGetProcAddress("glFramebufferRenderbuffer");
|
||||
if (!glFramebufferRenderbuffer)
|
||||
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) wglGetProcAddress("glFramebufferRenderbufferEXT");
|
||||
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) wglGetProcAddress("glCheckFramebufferStatus");
|
||||
if (!glCheckFramebufferStatus)
|
||||
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) wglGetProcAddress("glCheckFramebufferStatusEXT");
|
||||
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) wglGetProcAddress("glBlitFramebuffer");
|
||||
if (!glBlitFramebuffer)
|
||||
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) wglGetProcAddress("glBlitFramebufferEXT");
|
||||
glFenceSync = (PFNGLFENCESYNCPROC) wglGetProcAddress("glFenceSync");
|
||||
glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC) wglGetProcAddress("glClientWaitSync");
|
||||
glDeleteSync = (PFNGLDELETESYNCPROC) wglGetProcAddress("glDeleteSync");
|
||||
glGenBuffers = (PFNGLGENBUFFERSPROC) wglGetProcAddress("glGenBuffers");
|
||||
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
|
||||
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
|
||||
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
|
||||
glMapBuffer = (PFNGLMAPBUFFERPROC) wglGetProcAddress("glMapBuffer");
|
||||
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC) wglGetProcAddress("glUnmapBuffer");
|
||||
glBufferSubData = (PFNGLBUFFERSUBDATAPROC) wglGetProcAddress("glBufferSubData");
|
||||
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) wglGetProcAddress("glBindBufferBase");
|
||||
glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress("glActiveTexture");
|
||||
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) wglGetProcAddress("glGenVertexArrays");
|
||||
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) wglGetProcAddress("glDeleteVertexArrays");
|
||||
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC) wglGetProcAddress("glBindVertexArray");
|
||||
glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
|
||||
glDeleteShader = (PFNGLDELETESHADERPROC) wglGetProcAddress("glDeleteShader");
|
||||
glDeleteProgram = (PFNGLDELETEPROGRAMPROC) wglGetProcAddress("glDeleteProgram");
|
||||
glShaderSource = (PFNGLSHADERSOURCEPROC) wglGetProcAddress("glShaderSource");
|
||||
glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
|
||||
glGetShaderiv = (PFNGLGETSHADERIVPROC) wglGetProcAddress("glGetShaderiv");
|
||||
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) wglGetProcAddress("glGetShaderInfoLog");
|
||||
glCreateProgram = (PFNGLCREATEPROGRAMPROC) wglGetProcAddress("glCreateProgram");
|
||||
glAttachShader = (PFNGLATTACHSHADERPROC) wglGetProcAddress("glAttachShader");
|
||||
glLinkProgram = (PFNGLLINKPROGRAMPROC) wglGetProcAddress("glLinkProgram");
|
||||
glGetProgramiv = (PFNGLGETPROGRAMIVPROC) wglGetProcAddress("glGetProgramiv");
|
||||
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) wglGetProcAddress("glGetProgramInfoLog");
|
||||
glUseProgram = (PFNGLUSEPROGRAMPROC) wglGetProcAddress("glUseProgram");
|
||||
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress("glGetUniformLocation");
|
||||
glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC) wglGetProcAddress("glGetUniformBlockIndex");
|
||||
glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC) wglGetProcAddress("glUniformBlockBinding");
|
||||
glUniform1i = (PFNGLUNIFORM1IPROC) wglGetProcAddress("glUniform1i");
|
||||
glUniform1f = (PFNGLUNIFORM1FPROC) wglGetProcAddress("glUniform1f");
|
||||
glUniform2f = (PFNGLUNIFORM2FPROC) wglGetProcAddress("glUniform2f");
|
||||
glUniform4f = (PFNGLUNIFORM4FPROC) wglGetProcAddress("glUniform4f");
|
||||
|
||||
return glGenFramebuffers
|
||||
&& glGenRenderbuffers
|
||||
&& glBindRenderbuffer
|
||||
&& glRenderbufferStorage
|
||||
&& glDeleteFramebuffers
|
||||
&& glDeleteRenderbuffers
|
||||
&& glBindFramebuffer
|
||||
&& glFramebufferTexture2D
|
||||
&& glFramebufferRenderbuffer
|
||||
&& glCheckFramebufferStatus
|
||||
&& glBlitFramebuffer
|
||||
&& glFenceSync
|
||||
&& glClientWaitSync
|
||||
&& glDeleteSync
|
||||
&& glGenBuffers
|
||||
&& glDeleteBuffers
|
||||
&& glBindBuffer
|
||||
&& glBufferData
|
||||
&& glMapBuffer
|
||||
&& glUnmapBuffer
|
||||
&& glBufferSubData
|
||||
&& glBindBufferBase
|
||||
&& glActiveTexture
|
||||
&& glGenVertexArrays
|
||||
&& glDeleteVertexArrays
|
||||
&& glBindVertexArray
|
||||
&& glCreateShader
|
||||
&& glDeleteShader
|
||||
&& glDeleteProgram
|
||||
&& glShaderSource
|
||||
&& glCompileShader
|
||||
&& glGetShaderiv
|
||||
&& glGetShaderInfoLog
|
||||
&& glCreateProgram
|
||||
&& glAttachShader
|
||||
&& glLinkProgram
|
||||
&& glGetProgramiv
|
||||
&& glGetProgramInfoLog
|
||||
&& glUseProgram
|
||||
&& glGetUniformLocation
|
||||
&& glGetUniformBlockIndex
|
||||
&& glUniformBlockBinding
|
||||
&& glUniform1i
|
||||
&& glUniform1f
|
||||
&& glUniform2f
|
||||
&& glUniform4f
|
||||
;
|
||||
}
|
||||
201
src/render/GLExtensions.h
Normal file
201
src/render/GLExtensions.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/* -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-
|
||||
*/
|
||||
//
|
||||
// GLExtensions.h
|
||||
// LoopThroughWithOpenGLCompositing
|
||||
//
|
||||
|
||||
#ifndef __GLEXTENSIONS_H__
|
||||
#define __GLEXTENSIONS_H__
|
||||
|
||||
#include <windows.h>
|
||||
#include <gl/gl.h>
|
||||
|
||||
#ifndef APIENTRYP
|
||||
#define APIENTRYP APIENTRY *
|
||||
#endif
|
||||
|
||||
#define GL_BGRA 0x80E1
|
||||
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
|
||||
#define GL_STREAM_DRAW 0x88E0
|
||||
#define GL_STREAM_READ 0x88E1
|
||||
#define GL_STREAM_COPY 0x88E2
|
||||
#define GL_DYNAMIC_DRAW 0x88E8
|
||||
#define GL_UNIFORM_BUFFER 0x8A11
|
||||
#define GL_RGBA8 0x8058
|
||||
#define GL_RGBA16F 0x881A
|
||||
#define GL_TEXTURE0 0x84C0
|
||||
#define GL_ACTIVE_TEXTURE 0x84E0
|
||||
#define GL_ARRAY_BUFFER 0x8892
|
||||
#define GL_PIXEL_PACK_BUFFER 0x88EB
|
||||
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
|
||||
#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
|
||||
#define GL_FRAGMENT_SHADER 0x8B30
|
||||
#define GL_VERTEX_SHADER 0x8B31
|
||||
#define GL_COMPILE_STATUS 0x8B81
|
||||
#define GL_LINK_STATUS 0x8B82
|
||||
#define GL_INVALID_INDEX 0xFFFFFFFFu
|
||||
#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
|
||||
#define GL_RENDERBUFFER_EXT 0x8D41
|
||||
#define GL_FRAMEBUFFER_EXT 0x8D40
|
||||
#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5
|
||||
#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0
|
||||
#define GL_READ_FRAMEBUFFER 0x8CA8
|
||||
#define GL_DRAW_FRAMEBUFFER 0x8CA9
|
||||
#define GL_RENDERBUFFER 0x8D41
|
||||
#define GL_FRAMEBUFFER 0x8D40
|
||||
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
||||
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
||||
#define GL_DEPTH_COMPONENT24 0x81A6
|
||||
#define GL_CLAMP_TO_EDGE 0x812F
|
||||
#define GL_DEPTH_ATTACHMENT_EXT 0x8D00
|
||||
#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160
|
||||
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
||||
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
|
||||
#define GL_ALREADY_SIGNALED 0x911A
|
||||
#define GL_TIMEOUT_EXPIRED 0x911B
|
||||
#define GL_CONDITION_SATISFIED 0x911C
|
||||
#define GL_WAIT_FAILED 0x911D
|
||||
#define GL_READ_ONLY 0x88B8
|
||||
|
||||
typedef struct __GLsync *GLsync;
|
||||
typedef unsigned __int64 GLuint64;
|
||||
typedef ptrdiff_t GLintptr;
|
||||
typedef ptrdiff_t GLsizeiptr;
|
||||
typedef char GLchar;
|
||||
|
||||
typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
|
||||
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
|
||||
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
|
||||
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
|
||||
typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access);
|
||||
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target);
|
||||
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
|
||||
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
|
||||
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
|
||||
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
|
||||
typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
|
||||
typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
|
||||
typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
|
||||
typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
|
||||
typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
|
||||
typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
|
||||
typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);
|
||||
typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);
|
||||
typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length);
|
||||
typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1);
|
||||
typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
|
||||
typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar* uniformBlockName);
|
||||
typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
|
||||
typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);
|
||||
typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer);
|
||||
typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
|
||||
typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array);
|
||||
typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint* arrays);
|
||||
typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint* arrays);
|
||||
typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags);
|
||||
typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync);
|
||||
typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||
typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer);
|
||||
typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers);
|
||||
typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers);
|
||||
typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer);
|
||||
typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers);
|
||||
typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers);
|
||||
typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target);
|
||||
typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
|
||||
typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
|
||||
typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
|
||||
|
||||
extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
||||
extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
||||
extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
||||
extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
||||
extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
||||
extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
||||
extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
||||
extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
||||
extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
||||
extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
||||
extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
||||
extern PFNGLFENCESYNCPROC glFenceSync;
|
||||
extern PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
|
||||
extern PFNGLDELETESYNCPROC glDeleteSync;
|
||||
extern PFNGLGENBUFFERSPROC glGenBuffers;
|
||||
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
||||
extern PFNGLBUFFERDATAPROC glBufferData;
|
||||
extern PFNGLMAPBUFFERPROC glMapBuffer;
|
||||
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;
|
||||
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
|
||||
extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
|
||||
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
|
||||
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
||||
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
||||
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
||||
extern PFNGLCREATESHADERPROC glCreateShader;
|
||||
extern PFNGLDELETESHADERPROC glDeleteShader;
|
||||
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
||||
extern PFNGLSHADERSOURCEPROC glShaderSource;
|
||||
extern PFNGLCOMPILESHADERPROC glCompileShader;
|
||||
extern PFNGLGETSHADERIVPROC glGetShaderiv;
|
||||
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
||||
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||
extern PFNGLATTACHSHADERPROC glAttachShader;
|
||||
extern PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
extern PFNGLUSEPROGRAMPROC glUseProgram;
|
||||
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
|
||||
extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
|
||||
extern PFNGLUNIFORM1IPROC glUniform1i;
|
||||
extern PFNGLUNIFORM1FPROC glUniform1f;
|
||||
extern PFNGLUNIFORM2FPROC glUniform2f;
|
||||
extern PFNGLUNIFORM4FPROC glUniform4f;
|
||||
|
||||
bool ResolveGLExtensions();
|
||||
|
||||
#endif // __GLEXTENSIONS_H__
|
||||
|
||||
48
src/render/Std140Buffer.h
Normal file
48
src/render/Std140Buffer.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
inline std::size_t AlignStd140(std::size_t offset, std::size_t alignment)
|
||||
{
|
||||
const std::size_t mask = alignment - 1;
|
||||
return (offset + mask) & ~mask;
|
||||
}
|
||||
|
||||
template <typename TValue>
|
||||
inline void AppendStd140Value(std::vector<unsigned char>& buffer, std::size_t alignment, const TValue& value)
|
||||
{
|
||||
const std::size_t offset = AlignStd140(buffer.size(), alignment);
|
||||
if (buffer.size() < offset + sizeof(TValue))
|
||||
buffer.resize(offset + sizeof(TValue), 0);
|
||||
std::memcpy(buffer.data() + offset, &value, sizeof(TValue));
|
||||
}
|
||||
|
||||
inline void AppendStd140Float(std::vector<unsigned char>& buffer, float value)
|
||||
{
|
||||
AppendStd140Value(buffer, 4, value);
|
||||
}
|
||||
|
||||
inline void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
|
||||
{
|
||||
AppendStd140Value(buffer, 4, value);
|
||||
}
|
||||
|
||||
inline void AppendStd140Vec2(std::vector<unsigned char>& buffer, float x, float y)
|
||||
{
|
||||
const std::size_t offset = AlignStd140(buffer.size(), 8);
|
||||
if (buffer.size() < offset + sizeof(float) * 2)
|
||||
buffer.resize(offset + sizeof(float) * 2, 0);
|
||||
float values[2] = { x, y };
|
||||
std::memcpy(buffer.data() + offset, values, sizeof(values));
|
||||
}
|
||||
|
||||
inline void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, float z, float w)
|
||||
{
|
||||
const std::size_t offset = AlignStd140(buffer.size(), 16);
|
||||
if (buffer.size() < offset + sizeof(float) * 4)
|
||||
buffer.resize(offset + sizeof(float) * 4, 0);
|
||||
float values[4] = { x, y, z, w };
|
||||
std::memcpy(buffer.data() + offset, values, sizeof(values));
|
||||
}
|
||||
674
src/runtime/RuntimeJson.cpp
Normal file
674
src/runtime/RuntimeJson.cpp
Normal file
@@ -0,0 +1,674 @@
|
||||
#include "stdafx.h"
|
||||
#include "RuntimeJson.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
int HexDigitValue(char ch)
|
||||
{
|
||||
if (ch >= '0' && ch <= '9')
|
||||
return ch - '0';
|
||||
if (ch >= 'a' && ch <= 'f')
|
||||
return ch - 'a' + 10;
|
||||
if (ch >= 'A' && ch <= 'F')
|
||||
return ch - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool IsHighSurrogate(unsigned int codePoint)
|
||||
{
|
||||
return codePoint >= 0xD800 && codePoint <= 0xDBFF;
|
||||
}
|
||||
|
||||
bool IsLowSurrogate(unsigned int codePoint)
|
||||
{
|
||||
return codePoint >= 0xDC00 && codePoint <= 0xDFFF;
|
||||
}
|
||||
|
||||
void AppendUtf8(unsigned int codePoint, std::ostringstream& output)
|
||||
{
|
||||
if (codePoint <= 0x7F)
|
||||
{
|
||||
output << static_cast<char>(codePoint);
|
||||
}
|
||||
else if (codePoint <= 0x7FF)
|
||||
{
|
||||
output << static_cast<char>(0xC0 | ((codePoint >> 6) & 0x1F));
|
||||
output << static_cast<char>(0x80 | (codePoint & 0x3F));
|
||||
}
|
||||
else if (codePoint <= 0xFFFF)
|
||||
{
|
||||
output << static_cast<char>(0xE0 | ((codePoint >> 12) & 0x0F));
|
||||
output << static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
|
||||
output << static_cast<char>(0x80 | (codePoint & 0x3F));
|
||||
}
|
||||
else
|
||||
{
|
||||
output << static_cast<char>(0xF0 | ((codePoint >> 18) & 0x07));
|
||||
output << static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F));
|
||||
output << static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
|
||||
output << static_cast<char>(0x80 | (codePoint & 0x3F));
|
||||
}
|
||||
}
|
||||
|
||||
void AppendControlEscape(unsigned char ch, std::ostringstream& output)
|
||||
{
|
||||
const char* digits = "0123456789ABCDEF";
|
||||
output << "\\u00" << digits[(ch >> 4) & 0x0F] << digits[ch & 0x0F];
|
||||
}
|
||||
|
||||
class JsonParser
|
||||
{
|
||||
public:
|
||||
JsonParser(const std::string& text, std::string& error)
|
||||
: mText(text), mError(error), mPosition(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool parse(JsonValue& value)
|
||||
{
|
||||
skipWhitespace();
|
||||
if (!parseValue(value))
|
||||
return false;
|
||||
|
||||
skipWhitespace();
|
||||
if (mPosition != mText.size())
|
||||
{
|
||||
setError("Unexpected trailing characters in JSON input.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool parseValue(JsonValue& value)
|
||||
{
|
||||
if (mPosition >= mText.size())
|
||||
{
|
||||
setError("Unexpected end of JSON input.");
|
||||
return false;
|
||||
}
|
||||
|
||||
char ch = mText[mPosition];
|
||||
if (ch == '{')
|
||||
return parseObject(value);
|
||||
if (ch == '[')
|
||||
return parseArray(value);
|
||||
if (ch == '"')
|
||||
{
|
||||
std::string stringValue;
|
||||
if (!parseString(stringValue))
|
||||
return false;
|
||||
value = JsonValue(stringValue);
|
||||
return true;
|
||||
}
|
||||
if (ch == 't')
|
||||
return parseLiteral("true", JsonValue(true), value);
|
||||
if (ch == 'f')
|
||||
return parseLiteral("false", JsonValue(false), value);
|
||||
if (ch == 'n')
|
||||
return parseLiteral("null", JsonValue(), value);
|
||||
if (ch == '-' || std::isdigit(static_cast<unsigned char>(ch)))
|
||||
return parseNumber(value);
|
||||
|
||||
setError("Unexpected token while parsing JSON.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseObject(JsonValue& value)
|
||||
{
|
||||
value = JsonValue::MakeObject();
|
||||
++mPosition;
|
||||
skipWhitespace();
|
||||
if (consume('}'))
|
||||
return true;
|
||||
|
||||
while (mPosition < mText.size())
|
||||
{
|
||||
std::string key;
|
||||
if (!parseString(key))
|
||||
return false;
|
||||
|
||||
skipWhitespace();
|
||||
if (!consume(':'))
|
||||
{
|
||||
setError("Expected ':' after JSON object key.");
|
||||
return false;
|
||||
}
|
||||
|
||||
skipWhitespace();
|
||||
JsonValue item;
|
||||
if (!parseValue(item))
|
||||
return false;
|
||||
|
||||
value.set(key, item);
|
||||
|
||||
skipWhitespace();
|
||||
if (consume('}'))
|
||||
return true;
|
||||
if (!consume(','))
|
||||
{
|
||||
setError("Expected ',' or '}' in JSON object.");
|
||||
return false;
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
setError("Unexpected end of JSON object.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseArray(JsonValue& value)
|
||||
{
|
||||
value = JsonValue::MakeArray();
|
||||
++mPosition;
|
||||
skipWhitespace();
|
||||
if (consume(']'))
|
||||
return true;
|
||||
|
||||
while (mPosition < mText.size())
|
||||
{
|
||||
JsonValue item;
|
||||
if (!parseValue(item))
|
||||
return false;
|
||||
|
||||
value.pushBack(item);
|
||||
|
||||
skipWhitespace();
|
||||
if (consume(']'))
|
||||
return true;
|
||||
if (!consume(','))
|
||||
{
|
||||
setError("Expected ',' or ']' in JSON array.");
|
||||
return false;
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
setError("Unexpected end of JSON array.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseString(std::string& value)
|
||||
{
|
||||
if (!consume('"'))
|
||||
{
|
||||
setError("Expected string literal.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream result;
|
||||
while (mPosition < mText.size())
|
||||
{
|
||||
char ch = mText[mPosition++];
|
||||
if (ch == '"')
|
||||
{
|
||||
value = result.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ch == '\\')
|
||||
{
|
||||
if (mPosition >= mText.size())
|
||||
{
|
||||
setError("Unexpected end of escaped JSON string.");
|
||||
return false;
|
||||
}
|
||||
|
||||
char escaped = mText[mPosition++];
|
||||
switch (escaped)
|
||||
{
|
||||
case '"': result << '"'; break;
|
||||
case '\\': result << '\\'; break;
|
||||
case '/': result << '/'; break;
|
||||
case 'b': result << '\b'; break;
|
||||
case 'f': result << '\f'; break;
|
||||
case 'n': result << '\n'; break;
|
||||
case 'r': result << '\r'; break;
|
||||
case 't': result << '\t'; break;
|
||||
case 'u':
|
||||
if (!parseUnicodeEscape(result))
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
setError("Invalid escape sequence in JSON string.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (static_cast<unsigned char>(ch) < 0x20)
|
||||
{
|
||||
setError("Unescaped control character in JSON string.");
|
||||
return false;
|
||||
}
|
||||
result << ch;
|
||||
}
|
||||
}
|
||||
|
||||
setError("Unexpected end of JSON string.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseHexCodePoint(unsigned int& codePoint)
|
||||
{
|
||||
if (mPosition + 4 > mText.size())
|
||||
{
|
||||
setError("Unexpected end of Unicode escape sequence.");
|
||||
return false;
|
||||
}
|
||||
|
||||
codePoint = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
const int digit = HexDigitValue(mText[mPosition + i]);
|
||||
if (digit < 0)
|
||||
{
|
||||
setError("Invalid Unicode escape sequence in JSON string.");
|
||||
return false;
|
||||
}
|
||||
codePoint = (codePoint << 4) | static_cast<unsigned int>(digit);
|
||||
}
|
||||
|
||||
mPosition += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseUnicodeEscape(std::ostringstream& result)
|
||||
{
|
||||
unsigned int codePoint = 0;
|
||||
if (!parseHexCodePoint(codePoint))
|
||||
return false;
|
||||
|
||||
if (IsHighSurrogate(codePoint))
|
||||
{
|
||||
if (mPosition + 2 > mText.size() || mText[mPosition] != '\\' || mText[mPosition + 1] != 'u')
|
||||
{
|
||||
setError("High surrogate Unicode escape must be followed by a low surrogate.");
|
||||
return false;
|
||||
}
|
||||
|
||||
mPosition += 2;
|
||||
unsigned int lowSurrogate = 0;
|
||||
if (!parseHexCodePoint(lowSurrogate))
|
||||
return false;
|
||||
if (!IsLowSurrogate(lowSurrogate))
|
||||
{
|
||||
setError("High surrogate Unicode escape must be followed by a low surrogate.");
|
||||
return false;
|
||||
}
|
||||
|
||||
codePoint = 0x10000 + (((codePoint - 0xD800) << 10) | (lowSurrogate - 0xDC00));
|
||||
}
|
||||
else if (IsLowSurrogate(codePoint))
|
||||
{
|
||||
setError("Low surrogate Unicode escape without preceding high surrogate.");
|
||||
return false;
|
||||
}
|
||||
|
||||
AppendUtf8(codePoint, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseNumber(JsonValue& value)
|
||||
{
|
||||
std::size_t start = mPosition;
|
||||
|
||||
if (mText[mPosition] == '-')
|
||||
++mPosition;
|
||||
|
||||
if (mPosition >= mText.size())
|
||||
{
|
||||
setError("Invalid JSON number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mText[mPosition] == '0')
|
||||
{
|
||||
++mPosition;
|
||||
if (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
||||
{
|
||||
setError("JSON numbers must not contain leading zeroes.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mText[mPosition] >= '1' && mText[mPosition] <= '9')
|
||||
{
|
||||
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
||||
++mPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
setError("Invalid JSON number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mPosition < mText.size() && mText[mPosition] == '.')
|
||||
{
|
||||
++mPosition;
|
||||
if (mPosition >= mText.size() || !std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
||||
{
|
||||
setError("JSON number fraction must contain at least one digit.");
|
||||
return false;
|
||||
}
|
||||
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
||||
++mPosition;
|
||||
}
|
||||
|
||||
if (mPosition < mText.size() && (mText[mPosition] == 'e' || mText[mPosition] == 'E'))
|
||||
{
|
||||
++mPosition;
|
||||
if (mPosition < mText.size() && (mText[mPosition] == '+' || mText[mPosition] == '-'))
|
||||
++mPosition;
|
||||
if (mPosition >= mText.size() || !std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
||||
{
|
||||
setError("JSON number exponent must contain at least one digit.");
|
||||
return false;
|
||||
}
|
||||
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
||||
++mPosition;
|
||||
}
|
||||
|
||||
std::string token = mText.substr(start, mPosition - start);
|
||||
char* endPtr = nullptr;
|
||||
errno = 0;
|
||||
double parsed = strtod(token.c_str(), &endPtr);
|
||||
if (endPtr == token.c_str() || *endPtr != '\0' || errno == ERANGE || !std::isfinite(parsed))
|
||||
{
|
||||
setError("Invalid JSON number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
value = JsonValue(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseLiteral(const char* literal, const JsonValue& literalValue, JsonValue& value)
|
||||
{
|
||||
std::size_t length = strlen(literal);
|
||||
if (mText.compare(mPosition, length, literal) != 0)
|
||||
{
|
||||
setError("Invalid JSON literal.");
|
||||
return false;
|
||||
}
|
||||
|
||||
mPosition += length;
|
||||
value = literalValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
void skipWhitespace()
|
||||
{
|
||||
while (mPosition < mText.size() && std::isspace(static_cast<unsigned char>(mText[mPosition])))
|
||||
++mPosition;
|
||||
}
|
||||
|
||||
bool consume(char expected)
|
||||
{
|
||||
if (mPosition < mText.size() && mText[mPosition] == expected)
|
||||
{
|
||||
++mPosition;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setError(const std::string& error)
|
||||
{
|
||||
if (mError.empty())
|
||||
mError = error;
|
||||
}
|
||||
|
||||
const std::string& mText;
|
||||
std::string& mError;
|
||||
std::size_t mPosition;
|
||||
};
|
||||
|
||||
void SerializeJsonImpl(const JsonValue& value, std::ostringstream& output, bool pretty, int indentLevel)
|
||||
{
|
||||
auto indent = [&](int level) {
|
||||
if (!pretty)
|
||||
return;
|
||||
for (int i = 0; i < level; ++i)
|
||||
output << " ";
|
||||
};
|
||||
|
||||
switch (value.type())
|
||||
{
|
||||
case JsonValue::Type::Null:
|
||||
output << "null";
|
||||
break;
|
||||
case JsonValue::Type::Boolean:
|
||||
output << (value.asBoolean() ? "true" : "false");
|
||||
break;
|
||||
case JsonValue::Type::Number:
|
||||
{
|
||||
double number = value.asNumber();
|
||||
if (std::isfinite(number))
|
||||
{
|
||||
output << std::setprecision(15) << number;
|
||||
}
|
||||
else
|
||||
{
|
||||
output << "0";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JsonValue::Type::String:
|
||||
{
|
||||
output << '"';
|
||||
for (char ch : value.asString())
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case '"': output << "\\\""; break;
|
||||
case '\\': output << "\\\\"; break;
|
||||
case '\b': output << "\\b"; break;
|
||||
case '\f': output << "\\f"; break;
|
||||
case '\n': output << "\\n"; break;
|
||||
case '\r': output << "\\r"; break;
|
||||
case '\t': output << "\\t"; break;
|
||||
default:
|
||||
if (static_cast<unsigned char>(ch) < 0x20)
|
||||
AppendControlEscape(static_cast<unsigned char>(ch), output);
|
||||
else
|
||||
output << ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
output << '"';
|
||||
break;
|
||||
}
|
||||
case JsonValue::Type::Array:
|
||||
{
|
||||
output << "[";
|
||||
const std::vector<JsonValue>& array = value.asArray();
|
||||
if (!array.empty())
|
||||
{
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
for (std::size_t i = 0; i < array.size(); ++i)
|
||||
{
|
||||
indent(indentLevel + 1);
|
||||
SerializeJsonImpl(array[i], output, pretty, indentLevel + 1);
|
||||
if (i + 1 != array.size())
|
||||
output << ",";
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
}
|
||||
indent(indentLevel);
|
||||
}
|
||||
output << "]";
|
||||
break;
|
||||
}
|
||||
case JsonValue::Type::Object:
|
||||
{
|
||||
output << "{";
|
||||
const std::map<std::string, JsonValue>& object = value.asObject();
|
||||
if (!object.empty())
|
||||
{
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
std::size_t index = 0;
|
||||
for (const auto& item : object)
|
||||
{
|
||||
indent(indentLevel + 1);
|
||||
SerializeJsonImpl(JsonValue(item.first), output, pretty, indentLevel + 1);
|
||||
output << (pretty ? ": " : ":");
|
||||
SerializeJsonImpl(item.second, output, pretty, indentLevel + 1);
|
||||
if (++index != object.size())
|
||||
output << ",";
|
||||
if (pretty)
|
||||
output << "\n";
|
||||
}
|
||||
indent(indentLevel);
|
||||
}
|
||||
output << "}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonValue::JsonValue()
|
||||
: mType(Type::Null), mBooleanValue(false), mNumberValue(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(bool value)
|
||||
: mType(Type::Boolean), mBooleanValue(value), mNumberValue(0.0)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(double value)
|
||||
: mType(Type::Number), mBooleanValue(false), mNumberValue(value)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(const char* value)
|
||||
: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value ? value : "")
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue::JsonValue(const std::string& value)
|
||||
: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value)
|
||||
{
|
||||
}
|
||||
|
||||
JsonValue JsonValue::MakeArray()
|
||||
{
|
||||
JsonValue value;
|
||||
value.reset(Type::Array);
|
||||
return value;
|
||||
}
|
||||
|
||||
JsonValue JsonValue::MakeObject()
|
||||
{
|
||||
JsonValue value;
|
||||
value.reset(Type::Object);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool JsonValue::asBoolean(bool fallback) const
|
||||
{
|
||||
return mType == Type::Boolean ? mBooleanValue : fallback;
|
||||
}
|
||||
|
||||
double JsonValue::asNumber(double fallback) const
|
||||
{
|
||||
return mType == Type::Number ? mNumberValue : fallback;
|
||||
}
|
||||
|
||||
const std::string& JsonValue::asString() const
|
||||
{
|
||||
static const std::string emptyString;
|
||||
return mType == Type::String ? mStringValue : emptyString;
|
||||
}
|
||||
|
||||
const std::vector<JsonValue>& JsonValue::asArray() const
|
||||
{
|
||||
static const std::vector<JsonValue> emptyArray;
|
||||
return mType == Type::Array ? mArrayValue : emptyArray;
|
||||
}
|
||||
|
||||
const std::map<std::string, JsonValue>& JsonValue::asObject() const
|
||||
{
|
||||
static const std::map<std::string, JsonValue> emptyObject;
|
||||
return mType == Type::Object ? mObjectValue : emptyObject;
|
||||
}
|
||||
|
||||
std::vector<JsonValue>& JsonValue::array()
|
||||
{
|
||||
if (mType != Type::Array)
|
||||
reset(Type::Array);
|
||||
return mArrayValue;
|
||||
}
|
||||
|
||||
std::map<std::string, JsonValue>& JsonValue::object()
|
||||
{
|
||||
if (mType != Type::Object)
|
||||
reset(Type::Object);
|
||||
return mObjectValue;
|
||||
}
|
||||
|
||||
void JsonValue::pushBack(const JsonValue& value)
|
||||
{
|
||||
array().push_back(value);
|
||||
}
|
||||
|
||||
void JsonValue::set(const std::string& key, const JsonValue& value)
|
||||
{
|
||||
object()[key] = value;
|
||||
}
|
||||
|
||||
const JsonValue* JsonValue::find(const std::string& key) const
|
||||
{
|
||||
if (mType != Type::Object)
|
||||
return nullptr;
|
||||
|
||||
auto iterator = mObjectValue.find(key);
|
||||
return iterator != mObjectValue.end() ? &iterator->second : nullptr;
|
||||
}
|
||||
|
||||
void JsonValue::reset(Type type)
|
||||
{
|
||||
mType = type;
|
||||
mBooleanValue = false;
|
||||
mNumberValue = 0.0;
|
||||
mStringValue.clear();
|
||||
mArrayValue.clear();
|
||||
mObjectValue.clear();
|
||||
}
|
||||
|
||||
bool ParseJson(const std::string& text, JsonValue& value, std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
JsonParser parser(text, error);
|
||||
return parser.parse(value);
|
||||
}
|
||||
|
||||
std::string SerializeJson(const JsonValue& value, bool pretty)
|
||||
{
|
||||
std::ostringstream output;
|
||||
SerializeJsonImpl(value, output, pretty, 0);
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value)
|
||||
{
|
||||
std::vector<double> numbers;
|
||||
for (const JsonValue& item : value.asArray())
|
||||
{
|
||||
if (item.isNumber())
|
||||
numbers.push_back(item.asNumber());
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
65
src/runtime/RuntimeJson.h
Normal file
65
src/runtime/RuntimeJson.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class JsonValue
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
Null,
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Array,
|
||||
Object
|
||||
};
|
||||
|
||||
JsonValue();
|
||||
explicit JsonValue(bool value);
|
||||
explicit JsonValue(double value);
|
||||
explicit JsonValue(const char* value);
|
||||
explicit JsonValue(const std::string& value);
|
||||
|
||||
static JsonValue MakeArray();
|
||||
static JsonValue MakeObject();
|
||||
|
||||
Type type() const { return mType; }
|
||||
|
||||
bool isNull() const { return mType == Type::Null; }
|
||||
bool isBoolean() const { return mType == Type::Boolean; }
|
||||
bool isNumber() const { return mType == Type::Number; }
|
||||
bool isString() const { return mType == Type::String; }
|
||||
bool isArray() const { return mType == Type::Array; }
|
||||
bool isObject() const { return mType == Type::Object; }
|
||||
|
||||
bool asBoolean(bool fallback = false) const;
|
||||
double asNumber(double fallback = 0.0) const;
|
||||
const std::string& asString() const;
|
||||
const std::vector<JsonValue>& asArray() const;
|
||||
const std::map<std::string, JsonValue>& asObject() const;
|
||||
|
||||
std::vector<JsonValue>& array();
|
||||
std::map<std::string, JsonValue>& object();
|
||||
|
||||
void pushBack(const JsonValue& value);
|
||||
void set(const std::string& key, const JsonValue& value);
|
||||
|
||||
const JsonValue* find(const std::string& key) const;
|
||||
|
||||
private:
|
||||
void reset(Type type);
|
||||
|
||||
Type mType;
|
||||
bool mBooleanValue;
|
||||
double mNumberValue;
|
||||
std::string mStringValue;
|
||||
std::vector<JsonValue> mArrayValue;
|
||||
std::map<std::string, JsonValue> mObjectValue;
|
||||
};
|
||||
|
||||
bool ParseJson(const std::string& text, JsonValue& value, std::string& error);
|
||||
std::string SerializeJson(const JsonValue& value, bool pretty = false);
|
||||
std::vector<double> JsonArrayToNumbers(const JsonValue& value);
|
||||
196
src/runtime/RuntimeParameterUtils.cpp
Normal file
196
src/runtime/RuntimeParameterUtils.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "stdafx.h"
|
||||
#include "RuntimeParameterUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string TrimText(const std::string& text)
|
||||
{
|
||||
std::size_t start = 0;
|
||||
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
|
||||
++start;
|
||||
|
||||
std::size_t end = text.size();
|
||||
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
|
||||
--end;
|
||||
|
||||
return text.substr(start, end - start);
|
||||
}
|
||||
|
||||
bool IsFiniteNumber(double value)
|
||||
{
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
std::string NormalizeTextValue(const std::string& text, unsigned maxLength)
|
||||
{
|
||||
std::string normalized;
|
||||
normalized.reserve(std::min<std::size_t>(text.size(), maxLength));
|
||||
for (unsigned char ch : text)
|
||||
{
|
||||
if (ch < 32 || ch > 126)
|
||||
continue;
|
||||
if (normalized.size() >= maxLength)
|
||||
break;
|
||||
normalized.push_back(static_cast<char>(ch));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName)
|
||||
{
|
||||
std::string trimmed = TrimText(presetName);
|
||||
std::string safe;
|
||||
safe.reserve(trimmed.size());
|
||||
|
||||
for (unsigned char ch : trimmed)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
safe.push_back(static_cast<char>(std::tolower(ch)));
|
||||
else if (ch == ' ' || ch == '-' || ch == '_')
|
||||
{
|
||||
if (safe.empty() || safe.back() == '-')
|
||||
continue;
|
||||
safe.push_back('-');
|
||||
}
|
||||
}
|
||||
|
||||
while (!safe.empty() && safe.back() == '-')
|
||||
safe.pop_back();
|
||||
|
||||
return safe;
|
||||
}
|
||||
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition)
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
value.numberValues = definition.defaultNumbers.empty() ? std::vector<double>{ 0.0 } : definition.defaultNumbers;
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
value.numberValues = definition.defaultNumbers.size() == 2 ? definition.defaultNumbers : std::vector<double>{ 0.0, 0.0 };
|
||||
break;
|
||||
case ShaderParameterType::Color:
|
||||
value.numberValues = definition.defaultNumbers.size() == 4 ? definition.defaultNumbers : std::vector<double>{ 1.0, 1.0, 1.0, 1.0 };
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
value.booleanValue = definition.defaultBoolean;
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
value.enumValue = definition.defaultEnumValue;
|
||||
break;
|
||||
case ShaderParameterType::Text:
|
||||
value.textValue = NormalizeTextValue(definition.defaultTextValue, definition.maxLength);
|
||||
break;
|
||||
case ShaderParameterType::Trigger:
|
||||
value.numberValues = { 0.0, -1000000.0 };
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool NormalizeAndValidateParameterValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error)
|
||||
{
|
||||
normalizedValue = DefaultValueForDefinition(definition);
|
||||
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
{
|
||||
if (!value.isNumber())
|
||||
{
|
||||
error = "Expected numeric value for float parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
double number = value.asNumber();
|
||||
if (!IsFiniteNumber(number))
|
||||
{
|
||||
error = "Float parameter '" + definition.id + "' must be finite.";
|
||||
return false;
|
||||
}
|
||||
if (!definition.minNumbers.empty())
|
||||
number = std::max(number, definition.minNumbers.front());
|
||||
if (!definition.maxNumbers.empty())
|
||||
number = std::min(number, definition.maxNumbers.front());
|
||||
normalizedValue.numberValues = { number };
|
||||
return true;
|
||||
}
|
||||
case ShaderParameterType::Vec2:
|
||||
case ShaderParameterType::Color:
|
||||
{
|
||||
std::vector<double> numbers = JsonArrayToNumbers(value);
|
||||
const std::size_t expectedSize = definition.type == ShaderParameterType::Vec2 ? 2 : 4;
|
||||
if (numbers.size() != expectedSize)
|
||||
{
|
||||
error = "Expected array value of size " + std::to_string(expectedSize) + " for parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
for (std::size_t index = 0; index < numbers.size(); ++index)
|
||||
{
|
||||
if (!IsFiniteNumber(numbers[index]))
|
||||
{
|
||||
error = "Parameter '" + definition.id + "' contains a non-finite value.";
|
||||
return false;
|
||||
}
|
||||
if (index < definition.minNumbers.size())
|
||||
numbers[index] = std::max(numbers[index], definition.minNumbers[index]);
|
||||
if (index < definition.maxNumbers.size())
|
||||
numbers[index] = std::min(numbers[index], definition.maxNumbers[index]);
|
||||
}
|
||||
normalizedValue.numberValues = numbers;
|
||||
return true;
|
||||
}
|
||||
case ShaderParameterType::Boolean:
|
||||
if (!value.isBoolean())
|
||||
{
|
||||
error = "Expected boolean value for parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
normalizedValue.booleanValue = value.asBoolean();
|
||||
return true;
|
||||
case ShaderParameterType::Enum:
|
||||
{
|
||||
if (!value.isString())
|
||||
{
|
||||
error = "Expected string value for enum parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
const std::string selectedValue = value.asString();
|
||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
||||
{
|
||||
if (option.value == selectedValue)
|
||||
{
|
||||
normalizedValue.enumValue = selectedValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
error = "Enum parameter '" + definition.id + "' received unsupported option '" + selectedValue + "'.";
|
||||
return false;
|
||||
}
|
||||
case ShaderParameterType::Text:
|
||||
if (!value.isString())
|
||||
{
|
||||
error = "Expected string value for text parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
normalizedValue.textValue = NormalizeTextValue(value.asString(), definition.maxLength);
|
||||
return true;
|
||||
case ShaderParameterType::Trigger:
|
||||
if (!value.isNumber() && !value.isBoolean())
|
||||
{
|
||||
error = "Expected numeric or boolean value for trigger parameter '" + definition.id + "'.";
|
||||
return false;
|
||||
}
|
||||
normalizedValue.numberValues = { value.isNumber() ? std::max(0.0, std::floor(value.asNumber())) : 0.0, -1000000.0 };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
10
src/runtime/RuntimeParameterUtils.h
Normal file
10
src/runtime/RuntimeParameterUtils.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
std::string MakeSafePresetFileStem(const std::string& presetName);
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition);
|
||||
bool NormalizeAndValidateParameterValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error);
|
||||
326
src/shader/ShaderCompiler.cpp
Normal file
326
src/shader/ShaderCompiler.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
#include "stdafx.h"
|
||||
#include "ShaderCompiler.h"
|
||||
|
||||
#include "NativeHandles.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <cctype>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string ReplaceAll(std::string text, const std::string& from, const std::string& to)
|
||||
{
|
||||
std::size_t offset = 0;
|
||||
while ((offset = text.find(from, offset)) != std::string::npos)
|
||||
{
|
||||
text.replace(offset, from.length(), to);
|
||||
offset += to.length();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string SlangCBufferTypeForParameter(ShaderParameterType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ShaderParameterType::Float: return "float";
|
||||
case ShaderParameterType::Vec2: return "float2";
|
||||
case ShaderParameterType::Color: return "float4";
|
||||
case ShaderParameterType::Boolean: return "bool";
|
||||
case ShaderParameterType::Enum: return "int";
|
||||
case ShaderParameterType::Text: return "";
|
||||
case ShaderParameterType::Trigger: return "int";
|
||||
}
|
||||
return "float";
|
||||
}
|
||||
|
||||
std::string CapitalizeIdentifier(const std::string& identifier)
|
||||
{
|
||||
if (identifier.empty())
|
||||
return identifier;
|
||||
std::string text = identifier;
|
||||
text[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(text[0])));
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string BuildParameterUniforms(const std::vector<ShaderParameterDefinition>& parameters)
|
||||
{
|
||||
std::ostringstream source;
|
||||
for (const ShaderParameterDefinition& definition : parameters)
|
||||
{
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
continue;
|
||||
if (definition.type == ShaderParameterType::Trigger)
|
||||
{
|
||||
source << "\tint " << definition.id << ";\n";
|
||||
source << "\tfloat " << definition.id << "Time;\n";
|
||||
continue;
|
||||
}
|
||||
source << "\t" << SlangCBufferTypeForParameter(definition.type) << " " << definition.id << ";\n";
|
||||
}
|
||||
return source.str();
|
||||
}
|
||||
|
||||
std::string BuildHistorySamplerDeclarations(const std::string& samplerPrefix, unsigned historyLength)
|
||||
{
|
||||
std::ostringstream source;
|
||||
for (unsigned index = 0; index < historyLength; ++index)
|
||||
source << "Sampler2D<float4> " << samplerPrefix << index << ";\n";
|
||||
return source.str();
|
||||
}
|
||||
|
||||
std::string BuildTextureSamplerDeclarations(const std::vector<ShaderTextureAsset>& textureAssets)
|
||||
{
|
||||
std::ostringstream source;
|
||||
for (const ShaderTextureAsset& textureAsset : textureAssets)
|
||||
source << "Sampler2D<float4> " << textureAsset.id << ";\n";
|
||||
if (!textureAssets.empty())
|
||||
source << "\n";
|
||||
return source.str();
|
||||
}
|
||||
|
||||
std::string BuildTextSamplerDeclarations(const std::vector<ShaderParameterDefinition>& parameters)
|
||||
{
|
||||
std::ostringstream source;
|
||||
for (const ShaderParameterDefinition& definition : parameters)
|
||||
{
|
||||
if (definition.type != ShaderParameterType::Text)
|
||||
continue;
|
||||
source << "Sampler2D<float4> " << definition.id << "Texture;\n";
|
||||
}
|
||||
if (source.tellp() > 0)
|
||||
source << "\n";
|
||||
return source.str();
|
||||
}
|
||||
|
||||
std::string BuildTextHelpers(const std::vector<ShaderParameterDefinition>& parameters)
|
||||
{
|
||||
std::ostringstream source;
|
||||
for (const ShaderParameterDefinition& definition : parameters)
|
||||
{
|
||||
if (definition.type != ShaderParameterType::Text)
|
||||
continue;
|
||||
const std::string suffix = CapitalizeIdentifier(definition.id);
|
||||
source
|
||||
<< "float sample" << suffix << "(float2 uv)\n"
|
||||
<< "{\n"
|
||||
<< "\tif (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)\n"
|
||||
<< "\t\treturn 0.0;\n"
|
||||
<< "\treturn " << definition.id << "Texture.Sample(uv).r;\n"
|
||||
<< "}\n\n"
|
||||
<< "float4 draw" << suffix << "(float2 uv, float4 fillColor)\n"
|
||||
<< "{\n"
|
||||
<< "\tfloat alpha = sample" << suffix << "(uv) * fillColor.a;\n"
|
||||
<< "\treturn float4(fillColor.rgb * alpha, alpha);\n"
|
||||
<< "}\n\n";
|
||||
}
|
||||
return source.str();
|
||||
}
|
||||
|
||||
std::string BuildHistorySwitchCases(const std::string& samplerPrefix, unsigned historyLength)
|
||||
{
|
||||
std::ostringstream source;
|
||||
for (unsigned index = 0; index < historyLength; ++index)
|
||||
source << "\tcase " << index << ": return " << samplerPrefix << index << ".Sample(tc);\n";
|
||||
return source.str();
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCompiler::ShaderCompiler(
|
||||
const std::filesystem::path& repoRoot,
|
||||
const std::filesystem::path& wrapperPath,
|
||||
const std::filesystem::path& generatedGlslPath,
|
||||
const std::filesystem::path& patchedGlslPath,
|
||||
unsigned maxTemporalHistoryFrames)
|
||||
: mRepoRoot(repoRoot),
|
||||
mWrapperPath(wrapperPath),
|
||||
mGeneratedGlslPath(generatedGlslPath),
|
||||
mPatchedGlslPath(patchedGlslPath),
|
||||
mMaxTemporalHistoryFrames(maxTemporalHistoryFrames)
|
||||
{
|
||||
}
|
||||
|
||||
bool ShaderCompiler::BuildPassFragmentShaderSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& fragmentShaderSource, std::string& error) const
|
||||
{
|
||||
std::string wrapperSource;
|
||||
if (!BuildWrapperSlangSource(shaderPackage, pass, wrapperSource, error))
|
||||
return false;
|
||||
if (!WriteTextFile(mWrapperPath, wrapperSource, error))
|
||||
return false;
|
||||
|
||||
if (!RunSlangCompiler(mWrapperPath, mGeneratedGlslPath, error))
|
||||
return false;
|
||||
|
||||
fragmentShaderSource = ReadTextFile(mGeneratedGlslPath, error);
|
||||
if (fragmentShaderSource.empty())
|
||||
return false;
|
||||
|
||||
if (!PatchGeneratedGlsl(fragmentShaderSource, error))
|
||||
return false;
|
||||
|
||||
if (!WriteTextFile(mPatchedGlslPath, fragmentShaderSource, error))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderCompiler::BuildWrapperSlangSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& wrapperSource, std::string& error) const
|
||||
{
|
||||
const std::filesystem::path templatePath = mRepoRoot / "runtime" / "templates" / "shader_wrapper.slang.in";
|
||||
wrapperSource = ReadTextFile(templatePath, error);
|
||||
if (wrapperSource.empty())
|
||||
return false;
|
||||
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{PARAMETER_UNIFORMS}}", BuildParameterUniforms(shaderPackage.parameters));
|
||||
const unsigned historySamplerCount = shaderPackage.temporal.enabled ? mMaxTemporalHistoryFrames : 0;
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gSourceHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SAMPLERS}}", BuildHistorySamplerDeclarations("gTemporalHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{FEEDBACK_SAMPLER}}", shaderPackage.feedback.enabled ? "Sampler2D<float4> gFeedbackState;\n" : "");
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{FEEDBACK_HELPER}}",
|
||||
shaderPackage.feedback.enabled
|
||||
? "float4 sampleFeedback(float2 tc)\n{\n\tif (gFeedbackAvailable <= 0)\n\t\treturn float4(0.0, 0.0, 0.0, 0.0);\n\treturn gFeedbackState.Sample(tc);\n}\n"
|
||||
: "float4 sampleFeedback(float2 tc)\n{\n\treturn float4(0.0, 0.0, 0.0, 0.0);\n}\n");
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXTURE_SAMPLERS}}", BuildTextureSamplerDeclarations(shaderPackage.textureAssets));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_SAMPLERS}}", BuildTextSamplerDeclarations(shaderPackage.parameters));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEXT_HELPERS}}", BuildTextHelpers(shaderPackage.parameters));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{SOURCE_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gSourceHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{TEMPORAL_HISTORY_SWITCH_CASES}}", BuildHistorySwitchCases("gTemporalHistory", historySamplerCount));
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{USER_SHADER_INCLUDE}}", pass.sourcePath.generic_string());
|
||||
wrapperSource = ReplaceAll(wrapperSource, "{{ENTRY_POINT_CALL}}", pass.entryPoint + "(context)");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderCompiler::FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const
|
||||
{
|
||||
char slangRootBuffer[MAX_PATH] = {};
|
||||
const DWORD slangRootLength = GetEnvironmentVariableA("SLANG_ROOT", slangRootBuffer, static_cast<DWORD>(sizeof(slangRootBuffer)));
|
||||
if (slangRootLength > 0 && slangRootLength < sizeof(slangRootBuffer))
|
||||
{
|
||||
std::filesystem::path candidate = std::filesystem::path(slangRootBuffer) / "bin" / "slangc.exe";
|
||||
if (std::filesystem::exists(candidate))
|
||||
{
|
||||
compilerPath = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path thirdPartyRoot = mRepoRoot / "3rdParty";
|
||||
if (!std::filesystem::exists(thirdPartyRoot))
|
||||
{
|
||||
error = "3rdParty directory was not found under the repository root.";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyRoot))
|
||||
{
|
||||
if (!entry.is_directory())
|
||||
continue;
|
||||
std::filesystem::path candidate = entry.path() / "bin" / "slangc.exe";
|
||||
if (std::filesystem::exists(candidate))
|
||||
{
|
||||
compilerPath = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
error = "Could not find slangc.exe under 3rdParty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShaderCompiler::RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const
|
||||
{
|
||||
std::filesystem::path compilerPath;
|
||||
if (!FindSlangCompiler(compilerPath, error))
|
||||
return false;
|
||||
|
||||
std::string commandLine = "\"" + compilerPath.string() + "\" \"" + wrapperPath.string()
|
||||
+ "\" -target glsl -profile glsl_430 -entry fragmentMain -stage fragment -o \"" + outputPath.string() + "\"";
|
||||
|
||||
STARTUPINFOA startupInfo = {};
|
||||
PROCESS_INFORMATION processInfo = {};
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
std::vector<char> mutableCommandLine(commandLine.begin(), commandLine.end());
|
||||
mutableCommandLine.push_back('\0');
|
||||
|
||||
if (!CreateProcessA(NULL, mutableCommandLine.data(), NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, mRepoRoot.string().c_str(), &startupInfo, &processInfo))
|
||||
{
|
||||
error = "Failed to launch slangc.exe.";
|
||||
return false;
|
||||
}
|
||||
|
||||
UniqueHandle processHandle(processInfo.hProcess);
|
||||
UniqueHandle threadHandle(processInfo.hThread);
|
||||
|
||||
WaitForSingleObject(processHandle.get(), INFINITE);
|
||||
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeProcess(processHandle.get(), &exitCode);
|
||||
|
||||
if (exitCode != 0)
|
||||
{
|
||||
error = "slangc.exe returned a non-zero exit code while compiling the active shader package.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderCompiler::PatchGeneratedGlsl(std::string& shaderText, std::string& error) const
|
||||
{
|
||||
if (shaderText.find("#version 450") == std::string::npos)
|
||||
{
|
||||
error = "Generated GLSL did not include the expected version header.";
|
||||
return false;
|
||||
}
|
||||
|
||||
shaderText = ReplaceAll(shaderText, "#version 450", "#version 430 core");
|
||||
shaderText = std::regex_replace(shaderText, std::regex(R"(#extension GL_EXT_samplerless_texture_functions : require\r?\n)"), "");
|
||||
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) uniform;\r?\n)"), "");
|
||||
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(row_major\) buffer;\r?\n)"), "");
|
||||
shaderText = std::regex_replace(shaderText, std::regex(R"(layout\(location = 0\)\s*in vec2 ([A-Za-z0-9_]+);)"), "in vec2 vTexCoord;");
|
||||
shaderText = ReplaceAll(shaderText, "input_texCoord_0", "vTexCoord");
|
||||
|
||||
std::smatch match;
|
||||
std::regex outRegex(R"(layout\(location = 0\)\s*out vec4 ([A-Za-z0-9_]+);)");
|
||||
if (std::regex_search(shaderText, match, outRegex))
|
||||
{
|
||||
const std::string outputName = match[1].str();
|
||||
shaderText = std::regex_replace(shaderText, outRegex, "layout(location = 0) out vec4 fragColor;");
|
||||
shaderText = ReplaceAll(shaderText, outputName + " =", "fragColor =");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ShaderCompiler::ReadTextFile(const std::filesystem::path& path, std::string& error) const
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
bool ShaderCompiler::WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const
|
||||
{
|
||||
std::error_code fsError;
|
||||
std::filesystem::create_directories(path.parent_path(), fsError);
|
||||
|
||||
std::ofstream output(path, std::ios::binary);
|
||||
if (!output)
|
||||
{
|
||||
error = "Could not write file: " + path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
output << contents;
|
||||
return output.good();
|
||||
}
|
||||
34
src/shader/ShaderCompiler.h
Normal file
34
src/shader/ShaderCompiler.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
class ShaderCompiler
|
||||
{
|
||||
public:
|
||||
ShaderCompiler(
|
||||
const std::filesystem::path& repoRoot,
|
||||
const std::filesystem::path& wrapperPath,
|
||||
const std::filesystem::path& generatedGlslPath,
|
||||
const std::filesystem::path& patchedGlslPath,
|
||||
unsigned maxTemporalHistoryFrames);
|
||||
|
||||
bool BuildPassFragmentShaderSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& fragmentShaderSource, std::string& error) const;
|
||||
|
||||
private:
|
||||
bool BuildWrapperSlangSource(const ShaderPackage& shaderPackage, const ShaderPassDefinition& pass, std::string& wrapperSource, std::string& error) const;
|
||||
bool FindSlangCompiler(std::filesystem::path& compilerPath, std::string& error) const;
|
||||
bool RunSlangCompiler(const std::filesystem::path& wrapperPath, const std::filesystem::path& outputPath, std::string& error) const;
|
||||
bool PatchGeneratedGlsl(std::string& shaderText, std::string& error) const;
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error) const;
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& contents, std::string& error) const;
|
||||
|
||||
private:
|
||||
std::filesystem::path mRepoRoot;
|
||||
std::filesystem::path mWrapperPath;
|
||||
std::filesystem::path mGeneratedGlslPath;
|
||||
std::filesystem::path mPatchedGlslPath;
|
||||
unsigned mMaxTemporalHistoryFrames;
|
||||
};
|
||||
818
src/shader/ShaderPackageRegistry.cpp
Normal file
818
src/shader/ShaderPackageRegistry.cpp
Normal file
@@ -0,0 +1,818 @@
|
||||
#include "stdafx.h"
|
||||
#include "ShaderPackageRegistry.h"
|
||||
|
||||
#include "RuntimeJson.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string Trim(const std::string& text)
|
||||
{
|
||||
std::size_t start = 0;
|
||||
while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
|
||||
++start;
|
||||
|
||||
std::size_t end = text.size();
|
||||
while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
|
||||
--end;
|
||||
|
||||
return text.substr(start, end - start);
|
||||
}
|
||||
|
||||
bool IsFiniteNumber(double value)
|
||||
{
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
|
||||
{
|
||||
if (typeName == "float")
|
||||
{
|
||||
type = ShaderParameterType::Float;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "vec2")
|
||||
{
|
||||
type = ShaderParameterType::Vec2;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "color")
|
||||
{
|
||||
type = ShaderParameterType::Color;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "bool")
|
||||
{
|
||||
type = ShaderParameterType::Boolean;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "enum")
|
||||
{
|
||||
type = ShaderParameterType::Enum;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "text")
|
||||
{
|
||||
type = ShaderParameterType::Text;
|
||||
return true;
|
||||
}
|
||||
if (typeName == "trigger")
|
||||
{
|
||||
type = ShaderParameterType::Trigger;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
|
||||
{
|
||||
if (sourceName == "source")
|
||||
{
|
||||
source = TemporalHistorySource::Source;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "preLayerInput")
|
||||
{
|
||||
source = TemporalHistorySource::PreLayerInput;
|
||||
return true;
|
||||
}
|
||||
if (sourceName == "none")
|
||||
{
|
||||
source = TemporalHistorySource::None;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input)
|
||||
{
|
||||
error = "Could not open file: " + path.string();
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
std::string ManifestPathMessage(const std::filesystem::path& manifestPath)
|
||||
{
|
||||
return manifestPath.string();
|
||||
}
|
||||
|
||||
bool RequireStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fieldValue = object.find(fieldName);
|
||||
if (!fieldValue || !fieldValue->isString())
|
||||
{
|
||||
error = "Shader manifest is missing required string '" + std::string(fieldName) + "' field: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = fieldValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RequireNonEmptyStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!RequireStringField(object, fieldName, value, manifestPath, error))
|
||||
return false;
|
||||
if (Trim(value).empty())
|
||||
{
|
||||
error = "Shader manifest string '" + std::string(fieldName) + "' must not be empty: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::string& fallback, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fieldValue = object.find(fieldName);
|
||||
if (!fieldValue)
|
||||
{
|
||||
value = fallback;
|
||||
return true;
|
||||
}
|
||||
if (!fieldValue->isString())
|
||||
{
|
||||
error = "Shader manifest field '" + std::string(fieldName) + "' must be a string in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
value = fieldValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalArrayField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
value = object.find(fieldName);
|
||||
if (!value)
|
||||
return true;
|
||||
if (!value->isArray())
|
||||
{
|
||||
error = "Shader manifest '" + std::string(fieldName) + "' field must be an array in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptionalObjectField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
value = object.find(fieldName);
|
||||
if (!value)
|
||||
return true;
|
||||
if (!value->isObject())
|
||||
{
|
||||
error = "Shader manifest '" + std::string(fieldName) + "' field must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NumberListFromJsonValue(const JsonValue& value, std::vector<double>& numbers, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (value.isNumber())
|
||||
{
|
||||
numbers.push_back(value.asNumber());
|
||||
return true;
|
||||
}
|
||||
if (value.isArray())
|
||||
{
|
||||
numbers = JsonArrayToNumbers(value);
|
||||
if (numbers.size() != value.asArray().size())
|
||||
{
|
||||
error = "Shader parameter field '" + fieldName + "' must contain only numbers in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
error = "Shader parameter field '" + fieldName + "' must be a number or array of numbers in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidateShaderIdentifier(const std::string& identifier, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (identifier.empty() || !(std::isalpha(static_cast<unsigned char>(identifier.front())) || identifier.front() == '_'))
|
||||
{
|
||||
error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (char ch : identifier)
|
||||
{
|
||||
const unsigned char unsignedCh = static_cast<unsigned char>(ch);
|
||||
if (!(std::isalnum(unsignedCh) || ch == '_'))
|
||||
{
|
||||
error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseShaderMetadata(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!RequireStringField(manifestJson, "id", shaderPackage.id, manifestPath, error) ||
|
||||
!RequireStringField(manifestJson, "name", shaderPackage.displayName, manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "description", shaderPackage.description, "", manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "category", shaderPackage.category, "", manifestPath, error) ||
|
||||
!OptionalStringField(manifestJson, "entryPoint", shaderPackage.entryPoint, "shadeVideo", manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidateShaderIdentifier(shaderPackage.entryPoint, "entryPoint", manifestPath, error))
|
||||
return false;
|
||||
|
||||
shaderPackage.directoryPath = manifestPath.parent_path();
|
||||
shaderPackage.shaderPath = shaderPackage.directoryPath / "shader.slang";
|
||||
shaderPackage.manifestPath = manifestPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* passesValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "passes", passesValue, manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!passesValue)
|
||||
{
|
||||
// Existing shader packages are treated as a single implicit pass, so
|
||||
// multipass support does not require manifest churn.
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = "main";
|
||||
pass.entryPoint = shaderPackage.entryPoint;
|
||||
pass.sourcePath = shaderPackage.shaderPath;
|
||||
pass.outputName = "layerOutput";
|
||||
if (!std::filesystem::exists(pass.sourcePath))
|
||||
{
|
||||
error = "Shader source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||
return false;
|
||||
}
|
||||
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||
shaderPackage.passes.push_back(pass);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (passesValue->asArray().empty())
|
||||
{
|
||||
error = "Shader manifest 'passes' field must not be empty in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const JsonValue& passJson : passesValue->asArray())
|
||||
{
|
||||
if (!passJson.isObject())
|
||||
{
|
||||
error = "Shader pass entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string passId;
|
||||
std::string sourcePath;
|
||||
if (!RequireNonEmptyStringField(passJson, "id", passId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(passJson, "source", sourcePath, manifestPath, error))
|
||||
{
|
||||
error = "Shader pass is missing required 'id' or 'source' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(passId, "passes[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
for (const ShaderPassDefinition& existingPass : shaderPackage.passes)
|
||||
{
|
||||
if (existingPass.id == passId)
|
||||
{
|
||||
error = "Duplicate shader pass id '" + passId + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPassDefinition pass;
|
||||
pass.id = passId;
|
||||
pass.sourcePath = shaderPackage.directoryPath / sourcePath;
|
||||
if (!OptionalStringField(passJson, "entryPoint", pass.entryPoint, shaderPackage.entryPoint, manifestPath, error) ||
|
||||
!OptionalStringField(passJson, "output", pass.outputName, passId, manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(pass.entryPoint, "passes[].entryPoint", manifestPath, error))
|
||||
return false;
|
||||
|
||||
const JsonValue* inputsValue = nullptr;
|
||||
if (!OptionalArrayField(passJson, "inputs", inputsValue, manifestPath, error))
|
||||
return false;
|
||||
if (inputsValue)
|
||||
{
|
||||
for (const JsonValue& inputValue : inputsValue->asArray())
|
||||
{
|
||||
if (!inputValue.isString())
|
||||
{
|
||||
error = "Shader pass inputs must be strings in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
pass.inputNames.push_back(inputValue.asString());
|
||||
}
|
||||
}
|
||||
|
||||
// Keep source validation in the registry. Bad pass declarations then
|
||||
// appear as unavailable shaders instead of failing at render time.
|
||||
if (!std::filesystem::exists(pass.sourcePath))
|
||||
{
|
||||
error = "Shader pass source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
|
||||
return false;
|
||||
}
|
||||
pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
|
||||
shaderPackage.passes.push_back(pass);
|
||||
}
|
||||
|
||||
shaderPackage.shaderPath = shaderPackage.passes.front().sourcePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* texturesValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "textures", texturesValue, manifestPath, error))
|
||||
return false;
|
||||
if (!texturesValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& textureJson : texturesValue->asArray())
|
||||
{
|
||||
if (!textureJson.isObject())
|
||||
{
|
||||
error = "Shader texture entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string textureId;
|
||||
std::string texturePath;
|
||||
if (!RequireNonEmptyStringField(textureJson, "id", textureId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(textureJson, "path", texturePath, manifestPath, error))
|
||||
{
|
||||
error = "Shader texture is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(textureId, "textures[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
ShaderTextureAsset textureAsset;
|
||||
textureAsset.id = textureId;
|
||||
textureAsset.path = shaderPackage.directoryPath / texturePath;
|
||||
if (!std::filesystem::exists(textureAsset.path))
|
||||
{
|
||||
error = "Shader texture asset not found for package " + shaderPackage.id + ": " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
textureAsset.writeTime = std::filesystem::last_write_time(textureAsset.path);
|
||||
shaderPackage.textureAssets.push_back(textureAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* fontsValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "fonts", fontsValue, manifestPath, error))
|
||||
return false;
|
||||
if (!fontsValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& fontJson : fontsValue->asArray())
|
||||
{
|
||||
if (!fontJson.isObject())
|
||||
{
|
||||
error = "Shader font entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string fontId;
|
||||
std::string fontPath;
|
||||
if (!RequireNonEmptyStringField(fontJson, "id", fontId, manifestPath, error) ||
|
||||
!RequireNonEmptyStringField(fontJson, "path", fontPath, manifestPath, error))
|
||||
{
|
||||
error = "Shader font is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(fontId, "fonts[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
ShaderFontAsset fontAsset;
|
||||
fontAsset.id = fontId;
|
||||
fontAsset.path = shaderPackage.directoryPath / fontPath;
|
||||
if (!std::filesystem::exists(fontAsset.path))
|
||||
{
|
||||
error = "Shader font asset not found for package " + shaderPackage.id + ": " + fontAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
fontAsset.writeTime = std::filesystem::last_write_time(fontAsset.path);
|
||||
shaderPackage.fontAssets.push_back(fontAsset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* temporalValue = nullptr;
|
||||
if (!OptionalObjectField(manifestJson, "temporal", temporalValue, manifestPath, error))
|
||||
return false;
|
||||
if (!temporalValue)
|
||||
return true;
|
||||
|
||||
const JsonValue* enabledValue = temporalValue->find("enabled");
|
||||
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||
return true;
|
||||
|
||||
std::string historySourceName;
|
||||
if (!RequireNonEmptyStringField(*temporalValue, "historySource", historySourceName, manifestPath, error))
|
||||
{
|
||||
error = "Temporal shader is missing required 'historySource' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const JsonValue* historyLengthValue = temporalValue->find("historyLength");
|
||||
if (!historyLengthValue || !historyLengthValue->isNumber())
|
||||
{
|
||||
error = "Temporal shader is missing required numeric 'historyLength' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
if (!ParseTemporalHistorySource(historySourceName, historySource))
|
||||
{
|
||||
error = "Unsupported temporal historySource '" + historySourceName + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double requestedHistoryLength = historyLengthValue->asNumber();
|
||||
if (!IsFiniteNumber(requestedHistoryLength) || requestedHistoryLength <= 0.0 || std::floor(requestedHistoryLength) != requestedHistoryLength)
|
||||
{
|
||||
error = "Temporal shader 'historyLength' must be a positive integer in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
shaderPackage.temporal.enabled = true;
|
||||
shaderPackage.temporal.historySource = historySource;
|
||||
shaderPackage.temporal.requestedHistoryLength = static_cast<unsigned>(requestedHistoryLength);
|
||||
shaderPackage.temporal.effectiveHistoryLength = std::min(shaderPackage.temporal.requestedHistoryLength, maxTemporalHistoryFrames);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFeedbackSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* feedbackValue = nullptr;
|
||||
if (!OptionalObjectField(manifestJson, "feedback", feedbackValue, manifestPath, error))
|
||||
return false;
|
||||
if (!feedbackValue)
|
||||
return true;
|
||||
|
||||
const JsonValue* enabledValue = feedbackValue->find("enabled");
|
||||
if (!enabledValue || !enabledValue->asBoolean(false))
|
||||
return true;
|
||||
|
||||
shaderPackage.feedback.enabled = true;
|
||||
if (!OptionalStringField(*feedbackValue, "writePass", shaderPackage.feedback.writePassId, "", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (shaderPackage.feedback.writePassId.empty())
|
||||
{
|
||||
if (shaderPackage.passes.empty())
|
||||
{
|
||||
error = "Feedback-enabled shader has no passes to target in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
shaderPackage.feedback.writePassId = shaderPackage.passes.back().id;
|
||||
}
|
||||
|
||||
if (!ValidateShaderIdentifier(shaderPackage.feedback.writePassId, "feedback.writePass", manifestPath, error))
|
||||
return false;
|
||||
|
||||
const auto passIt = std::find_if(shaderPackage.passes.begin(), shaderPackage.passes.end(),
|
||||
[&shaderPackage](const ShaderPassDefinition& pass) { return pass.id == shaderPackage.feedback.writePassId; });
|
||||
if (passIt == shaderPackage.passes.end())
|
||||
{
|
||||
error = "Feedback writePass '" + shaderPackage.feedback.writePassId + "' does not match any declared pass in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (const JsonValue* fieldValue = parameterJson.find(fieldName))
|
||||
return NumberListFromJsonValue(*fieldValue, values, fieldName, manifestPath, error);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* defaultValue = parameterJson.find("default");
|
||||
if (!defaultValue)
|
||||
return true;
|
||||
|
||||
if (definition.type == ShaderParameterType::Boolean)
|
||||
{
|
||||
if (!defaultValue->isBoolean())
|
||||
{
|
||||
error = "Boolean parameter default must be a boolean for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultBoolean = defaultValue->asBoolean(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
{
|
||||
if (!defaultValue->isString())
|
||||
{
|
||||
error = "Enum parameter default must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultEnumValue = defaultValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
if (!defaultValue->isString())
|
||||
{
|
||||
error = "Text parameter default must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.defaultTextValue = defaultValue->asString();
|
||||
return true;
|
||||
}
|
||||
|
||||
return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error);
|
||||
}
|
||||
|
||||
bool ParseParameterOptions(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* optionsValue = nullptr;
|
||||
if (!OptionalArrayField(parameterJson, "options", optionsValue, manifestPath, error) || !optionsValue)
|
||||
{
|
||||
error = "Enum parameter is missing 'options' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const JsonValue& optionJson : optionsValue->asArray())
|
||||
{
|
||||
if (!optionJson.isObject())
|
||||
{
|
||||
error = "Enum parameter option must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderParameterOption option;
|
||||
if (!RequireStringField(optionJson, "value", option.value, manifestPath, error) ||
|
||||
!RequireStringField(optionJson, "label", option.label, manifestPath, error))
|
||||
{
|
||||
error = "Enum parameter option is missing 'value' or 'label' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
definition.enumOptions.push_back(option);
|
||||
}
|
||||
|
||||
bool defaultFound = definition.defaultEnumValue.empty();
|
||||
for (const ShaderParameterOption& option : definition.enumOptions)
|
||||
{
|
||||
if (option.value == definition.defaultEnumValue)
|
||||
{
|
||||
defaultFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultFound)
|
||||
{
|
||||
error = "Enum parameter default is not present in its option list for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
if (!parameterJson.isObject())
|
||||
{
|
||||
error = "Shader parameter entry must be an object in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string typeName;
|
||||
if (!RequireStringField(parameterJson, "id", definition.id, manifestPath, error) ||
|
||||
!RequireStringField(parameterJson, "label", definition.label, manifestPath, error) ||
|
||||
!RequireStringField(parameterJson, "type", typeName, manifestPath, error))
|
||||
{
|
||||
error = "Shader parameter is missing required fields in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseShaderParameterType(typeName, definition.type))
|
||||
{
|
||||
error = "Unsupported parameter type '" + typeName + "' in: " + ManifestPathMessage(manifestPath);
|
||||
return false;
|
||||
}
|
||||
if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
|
||||
!ParseParameterNumberField(parameterJson, "step", definition.stepNumbers, manifestPath, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Text)
|
||||
{
|
||||
if (const JsonValue* fontValue = parameterJson.find("font"))
|
||||
{
|
||||
if (!fontValue->isString())
|
||||
{
|
||||
error = "Text parameter 'font' must be a string for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.fontId = fontValue->asString();
|
||||
if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
|
||||
return false;
|
||||
}
|
||||
if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
|
||||
{
|
||||
if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
|
||||
{
|
||||
error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id;
|
||||
return false;
|
||||
}
|
||||
definition.maxLength = static_cast<unsigned>(maxLengthValue->asNumber());
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type == ShaderParameterType::Enum)
|
||||
return ParseParameterOptions(parameterJson, definition, manifestPath, error);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
|
||||
{
|
||||
const JsonValue* parametersValue = nullptr;
|
||||
if (!OptionalArrayField(manifestJson, "parameters", parametersValue, manifestPath, error))
|
||||
return false;
|
||||
if (!parametersValue)
|
||||
return true;
|
||||
|
||||
for (const JsonValue& parameterJson : parametersValue->asArray())
|
||||
{
|
||||
ShaderParameterDefinition definition;
|
||||
if (!ParseParameterDefinition(parameterJson, definition, manifestPath, error))
|
||||
return false;
|
||||
shaderPackage.parameters.push_back(definition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string UniqueUnavailableShaderId(const std::filesystem::path& manifestPath, const std::string& parsedId)
|
||||
{
|
||||
const std::string fallbackId = manifestPath.parent_path().filename().string();
|
||||
const std::string baseId = parsedId.empty() ? fallbackId : parsedId;
|
||||
return baseId + "@invalid:" + fallbackId;
|
||||
}
|
||||
|
||||
ShaderPackageStatus BuildUnavailableStatus(const std::filesystem::path& manifestPath, const ShaderPackage& partialPackage, const std::string& packageError)
|
||||
{
|
||||
ShaderPackageStatus status;
|
||||
status.id = UniqueUnavailableShaderId(manifestPath, partialPackage.id);
|
||||
status.displayName = !partialPackage.displayName.empty() ? partialPackage.displayName : manifestPath.parent_path().filename().string();
|
||||
status.description = partialPackage.description;
|
||||
status.category = !partialPackage.category.empty() ? partialPackage.category : "Unavailable";
|
||||
status.available = false;
|
||||
status.error = packageError;
|
||||
return status;
|
||||
}
|
||||
|
||||
ShaderPackageStatus BuildAvailableStatus(const ShaderPackage& shaderPackage)
|
||||
{
|
||||
ShaderPackageStatus status;
|
||||
status.id = shaderPackage.id;
|
||||
status.displayName = shaderPackage.displayName;
|
||||
status.description = shaderPackage.description;
|
||||
status.category = shaderPackage.category;
|
||||
status.available = true;
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPackageRegistry::ShaderPackageRegistry(unsigned maxTemporalHistoryFrames)
|
||||
: mMaxTemporalHistoryFrames(maxTemporalHistoryFrames)
|
||||
{
|
||||
}
|
||||
|
||||
bool ShaderPackageRegistry::Scan(
|
||||
const std::filesystem::path& shaderRoot,
|
||||
std::map<std::string, ShaderPackage>& packagesById,
|
||||
std::vector<std::string>& packageOrder,
|
||||
std::vector<ShaderPackageStatus>& packageStatuses,
|
||||
std::string& error) const
|
||||
{
|
||||
packagesById.clear();
|
||||
packageOrder.clear();
|
||||
packageStatuses.clear();
|
||||
|
||||
if (!std::filesystem::exists(shaderRoot))
|
||||
{
|
||||
error = "Shader library directory does not exist: " + shaderRoot.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(shaderRoot))
|
||||
{
|
||||
if (!entry.is_directory())
|
||||
continue;
|
||||
|
||||
std::filesystem::path manifestPath = entry.path() / "shader.json";
|
||||
if (!std::filesystem::exists(manifestPath))
|
||||
continue;
|
||||
|
||||
ShaderPackage shaderPackage;
|
||||
if (!ParseManifest(manifestPath, shaderPackage, error))
|
||||
{
|
||||
packageStatuses.push_back(BuildUnavailableStatus(manifestPath, shaderPackage, error));
|
||||
error.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packagesById.find(shaderPackage.id) != packagesById.end())
|
||||
{
|
||||
packageStatuses.push_back(BuildUnavailableStatus(manifestPath, shaderPackage, "Duplicate shader id found: " + shaderPackage.id));
|
||||
continue;
|
||||
}
|
||||
|
||||
packageOrder.push_back(shaderPackage.id);
|
||||
packageStatuses.push_back(BuildAvailableStatus(shaderPackage));
|
||||
packagesById[shaderPackage.id] = shaderPackage;
|
||||
}
|
||||
|
||||
std::sort(packageOrder.begin(), packageOrder.end());
|
||||
std::sort(packageStatuses.begin(), packageStatuses.end(), [](const ShaderPackageStatus& left, const ShaderPackageStatus& right) {
|
||||
return left.displayName < right.displayName;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const
|
||||
{
|
||||
const std::string manifestText = ReadTextFile(manifestPath, error);
|
||||
if (manifestText.empty())
|
||||
return false;
|
||||
|
||||
JsonValue manifestJson;
|
||||
if (!ParseJson(manifestText, manifestJson, error))
|
||||
return false;
|
||||
if (!manifestJson.isObject())
|
||||
{
|
||||
error = "Shader manifest root must be an object: " + manifestPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
|
||||
return false;
|
||||
|
||||
if (!ParsePassDefinitions(manifestJson, shaderPackage, manifestPath, error))
|
||||
return false;
|
||||
|
||||
shaderPackage.shaderWriteTime = shaderPackage.passes.front().sourceWriteTime;
|
||||
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
||||
{
|
||||
if (pass.sourceWriteTime > shaderPackage.shaderWriteTime)
|
||||
shaderPackage.shaderWriteTime = pass.sourceWriteTime;
|
||||
}
|
||||
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
||||
|
||||
return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
||||
ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) &&
|
||||
ParseTemporalSettings(manifestJson, shaderPackage, mMaxTemporalHistoryFrames, manifestPath, error) &&
|
||||
ParseFeedbackSettings(manifestJson, shaderPackage, manifestPath, error) &&
|
||||
ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
|
||||
}
|
||||
25
src/shader/ShaderPackageRegistry.h
Normal file
25
src/shader/ShaderPackageRegistry.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ShaderPackageRegistry
|
||||
{
|
||||
public:
|
||||
explicit ShaderPackageRegistry(unsigned maxTemporalHistoryFrames);
|
||||
|
||||
bool Scan(
|
||||
const std::filesystem::path& shaderRoot,
|
||||
std::map<std::string, ShaderPackage>& packagesById,
|
||||
std::vector<std::string>& packageOrder,
|
||||
std::vector<ShaderPackageStatus>& packageStatuses,
|
||||
std::string& error) const;
|
||||
bool ParseManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const;
|
||||
|
||||
private:
|
||||
unsigned mMaxTemporalHistoryFrames;
|
||||
};
|
||||
159
src/shader/ShaderTypes.h
Normal file
159
src/shader/ShaderTypes.h
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class ShaderParameterType
|
||||
{
|
||||
Float,
|
||||
Vec2,
|
||||
Color,
|
||||
Boolean,
|
||||
Enum,
|
||||
Text,
|
||||
Trigger
|
||||
};
|
||||
|
||||
struct ShaderParameterOption
|
||||
{
|
||||
std::string value;
|
||||
std::string label;
|
||||
};
|
||||
|
||||
struct ShaderParameterDefinition
|
||||
{
|
||||
std::string id;
|
||||
std::string label;
|
||||
std::string description;
|
||||
ShaderParameterType type = ShaderParameterType::Float;
|
||||
std::vector<double> defaultNumbers;
|
||||
std::vector<double> minNumbers;
|
||||
std::vector<double> maxNumbers;
|
||||
std::vector<double> stepNumbers;
|
||||
bool defaultBoolean = false;
|
||||
std::string defaultEnumValue;
|
||||
std::string defaultTextValue;
|
||||
std::string fontId;
|
||||
unsigned maxLength = 64;
|
||||
std::vector<ShaderParameterOption> enumOptions;
|
||||
};
|
||||
|
||||
struct ShaderParameterValue
|
||||
{
|
||||
std::vector<double> numberValues;
|
||||
bool booleanValue = false;
|
||||
std::string enumValue;
|
||||
std::string textValue;
|
||||
};
|
||||
|
||||
enum class TemporalHistorySource
|
||||
{
|
||||
None,
|
||||
Source,
|
||||
PreLayerInput
|
||||
};
|
||||
|
||||
struct TemporalSettings
|
||||
{
|
||||
bool enabled = false;
|
||||
TemporalHistorySource historySource = TemporalHistorySource::None;
|
||||
unsigned requestedHistoryLength = 0;
|
||||
unsigned effectiveHistoryLength = 0;
|
||||
};
|
||||
|
||||
struct FeedbackSettings
|
||||
{
|
||||
bool enabled = false;
|
||||
std::string writePassId;
|
||||
};
|
||||
|
||||
struct ShaderTextureAsset
|
||||
{
|
||||
std::string id;
|
||||
std::filesystem::path path;
|
||||
std::filesystem::file_time_type writeTime;
|
||||
};
|
||||
|
||||
struct ShaderFontAsset
|
||||
{
|
||||
std::string id;
|
||||
std::filesystem::path path;
|
||||
std::filesystem::file_time_type writeTime;
|
||||
};
|
||||
|
||||
struct ShaderPassDefinition
|
||||
{
|
||||
std::string id;
|
||||
std::string entryPoint;
|
||||
std::filesystem::path sourcePath;
|
||||
std::filesystem::file_time_type sourceWriteTime;
|
||||
std::vector<std::string> inputNames;
|
||||
std::string outputName;
|
||||
};
|
||||
|
||||
struct ShaderPassBuildSource
|
||||
{
|
||||
std::string passId;
|
||||
std::string fragmentShaderSource;
|
||||
std::vector<std::string> inputNames;
|
||||
std::string outputName;
|
||||
};
|
||||
|
||||
struct ShaderPackage
|
||||
{
|
||||
std::string id;
|
||||
std::string displayName;
|
||||
std::string description;
|
||||
std::string category;
|
||||
std::string entryPoint;
|
||||
std::filesystem::path directoryPath;
|
||||
std::filesystem::path shaderPath;
|
||||
std::filesystem::path manifestPath;
|
||||
std::vector<ShaderPassDefinition> passes;
|
||||
std::vector<ShaderParameterDefinition> parameters;
|
||||
std::vector<ShaderTextureAsset> textureAssets;
|
||||
std::vector<ShaderFontAsset> fontAssets;
|
||||
TemporalSettings temporal;
|
||||
FeedbackSettings feedback;
|
||||
std::filesystem::file_time_type shaderWriteTime;
|
||||
std::filesystem::file_time_type manifestWriteTime;
|
||||
};
|
||||
|
||||
struct ShaderPackageStatus
|
||||
{
|
||||
std::string id;
|
||||
std::string displayName;
|
||||
std::string description;
|
||||
std::string category;
|
||||
bool available = false;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct RuntimeRenderState
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string shaderName;
|
||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||
std::vector<ShaderTextureAsset> textureAssets;
|
||||
std::vector<ShaderFontAsset> fontAssets;
|
||||
double timeSeconds = 0.0;
|
||||
double utcTimeSeconds = 0.0;
|
||||
double utcOffsetSeconds = 0.0;
|
||||
double startupRandom = 0.0;
|
||||
double frameCount = 0.0;
|
||||
double mixAmount = 1.0;
|
||||
double bypass = 0.0;
|
||||
unsigned inputWidth = 0;
|
||||
unsigned inputHeight = 0;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
bool isTemporal = false;
|
||||
TemporalHistorySource temporalHistorySource = TemporalHistorySource::None;
|
||||
unsigned requestedTemporalHistoryLength = 0;
|
||||
unsigned effectiveTemporalHistoryLength = 0;
|
||||
FeedbackSettings feedback;
|
||||
};
|
||||
13
src/stdafx.h
Normal file
13
src/stdafx.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
20433
src/video/DeckLinkAPI_h.h
Normal file
20433
src/video/DeckLinkAPI_h.h
Normal file
File diff suppressed because it is too large
Load Diff
486
src/video/DeckLinkAPI_i.c
Normal file
486
src/video/DeckLinkAPI_i.c
Normal file
@@ -0,0 +1,486 @@
|
||||
|
||||
|
||||
/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */
|
||||
|
||||
/* link this file in with the server and any clients */
|
||||
|
||||
|
||||
/* File created by MIDL compiler version 8.01.0628 */
|
||||
/* at Tue Jan 19 14:14:07 2038
|
||||
*/
|
||||
/* Compiler settings for ..\..\3rdParty\Blackmagic DeckLink SDK 16.0\Win\include\DeckLinkAPI.idl:
|
||||
Oicf, W1, Zp8, env=Win64 (32b run), target_arch=AMD64 8.01.0628
|
||||
protocol : all , ms_ext, c_ext, robust
|
||||
error checks: allocation ref bounds_check enum stub_data
|
||||
VC __declspec() decoration level:
|
||||
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
|
||||
DECLSPEC_UUID(), MIDL_INTERFACE()
|
||||
*/
|
||||
/* @@MIDL_FILE_HEADING( ) */
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif
|
||||
|
||||
|
||||
#include <rpc.h>
|
||||
#include <rpcndr.h>
|
||||
|
||||
#ifdef _MIDL_USE_GUIDDEF_
|
||||
|
||||
#ifndef INITGUID
|
||||
#define INITGUID
|
||||
#include <guiddef.h>
|
||||
#undef INITGUID
|
||||
#else
|
||||
#include <guiddef.h>
|
||||
#endif
|
||||
|
||||
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
|
||||
DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)
|
||||
|
||||
#else // !_MIDL_USE_GUIDDEF_
|
||||
|
||||
#ifndef __IID_DEFINED__
|
||||
#define __IID_DEFINED__
|
||||
|
||||
typedef struct _IID
|
||||
{
|
||||
unsigned long x;
|
||||
unsigned short s1;
|
||||
unsigned short s2;
|
||||
unsigned char c[8];
|
||||
} IID;
|
||||
|
||||
#endif // __IID_DEFINED__
|
||||
|
||||
#ifndef CLSID_DEFINED
|
||||
#define CLSID_DEFINED
|
||||
typedef IID CLSID;
|
||||
#endif // CLSID_DEFINED
|
||||
|
||||
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
|
||||
EXTERN_C __declspec(selectany) const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
|
||||
|
||||
#endif // !_MIDL_USE_GUIDDEF_
|
||||
|
||||
MIDL_DEFINE_GUID(IID, LIBID_DeckLinkAPI,0xD864517A,0xEDD5,0x466D,0x86,0x7D,0xC8,0x19,0xF1,0xC0,0x52,0xBB);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkTimecode,0xBC6CFBD3,0x8317,0x4325,0xAC,0x1C,0x12,0x16,0x39,0x1E,0x93,0x40);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayModeIterator,0x9C88499F,0xF601,0x4021,0xB8,0x0B,0x03,0x2E,0x4E,0xB4,0x1C,0x35);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayMode,0x3EB2C1AB,0x0A3D,0x4523,0xA3,0xAD,0xF4,0x0D,0x7F,0xB1,0x4E,0x78);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLink,0xC418FBDD,0x0587,0x48ED,0x8F,0xE5,0x64,0x0F,0x0A,0x14,0xAF,0x91);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration,0x5a68ffd4,0x1c12,0x4ede,0xa6,0xd2,0x45,0x45,0x1d,0x38,0x5f,0xc1);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderConfiguration,0x138050E5,0xC60A,0x4552,0xBF,0x3F,0x0F,0x35,0x80,0x49,0x32,0x7E);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeckControlStatusCallback,0x53436FFB,0xB434,0x4906,0xBA,0xDC,0xAE,0x30,0x60,0xFF,0xE8,0xEF);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeckControl,0x8E1C3ACE,0x19C7,0x4E00,0x8B,0x92,0xD8,0x04,0x31,0xD9,0x58,0xBE);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingDeviceNotificationCallback,0xF9531D64,0x3305,0x4B29,0xA3,0x87,0x7F,0x74,0xBB,0x0D,0x0E,0x84);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingH264InputCallback,0x823C475F,0x55AE,0x46F9,0x89,0x0C,0x53,0x7C,0xC5,0xCE,0xDC,0xCA);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingDiscovery,0x2C837444,0xF989,0x4D87,0x90,0x1A,0x47,0xC8,0xA3,0x6D,0x09,0x6D);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingVideoEncodingMode,0x1AB8035B,0xCD13,0x458D,0xB6,0xDF,0x5E,0x8F,0x7C,0x21,0x41,0xD9);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingMutableVideoEncodingMode,0x19BF7D90,0x1E0A,0x400D,0xB2,0xC6,0xFF,0xC4,0xE7,0x8A,0xD4,0x9D);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingVideoEncodingModePresetIterator,0x7AC731A3,0xC950,0x4AD0,0x80,0x4A,0x83,0x77,0xAA,0x51,0xC6,0xC4);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingDeviceInput,0x24B6B6EC,0x1727,0x44BB,0x98,0x18,0x34,0xFF,0x08,0x6A,0xCF,0x98);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingH264NALPacket,0xE260E955,0x14BE,0x4395,0x97,0x75,0x9F,0x02,0xCC,0x0A,0x9D,0x89);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingAudioPacket,0xD9EB5902,0x1AD2,0x43F4,0x9E,0x2C,0x3C,0xFA,0x50,0xB5,0xEE,0x19);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingMPEG2TSPacket,0x91810D1C,0x4FB3,0x4AAA,0xAE,0x56,0xFA,0x30,0x1D,0x3D,0xFA,0x4C);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IBMDStreamingH264NALParser,0x5867F18C,0x5BFA,0x4CCC,0xB2,0xA7,0x9D,0xFD,0x14,0x04,0x17,0xD2);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CBMDStreamingDiscovery,0x23A4EDF5,0xA0E5,0x432C,0x94,0xEF,0x3B,0xAB,0xB5,0xF8,0x1C,0x82);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CBMDStreamingH264NALParser,0x7753EFBD,0x951C,0x407C,0x97,0xA5,0x23,0xC7,0x37,0xB7,0x3B,0x52);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback,0x5BE6DF26,0x02CE,0x433E,0x99,0xD9,0x9A,0x87,0xC3,0xAC,0x17,0x1F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback,0x3A94F075,0xC37D,0x4BA8,0xBC,0xC0,0x1D,0x77,0x8C,0x8F,0x88,0x1B);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInputCallback,0xACF13E61,0xF4A0,0x4974,0xA6,0xA7,0x59,0xAF,0xF6,0x26,0x8B,0x31);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocator,0xF35DFA8D,0x9078,0x4622,0x95,0xBB,0x56,0x89,0x40,0x54,0xEB,0x0F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocatorProvider,0x6DF6F20A,0xD8DF,0x45D2,0x89,0x14,0x38,0x3C,0xE7,0xE6,0x24,0x3F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioOutputCallback,0x403C681B,0x7F46,0x4A12,0xB9,0x93,0x2B,0xB1,0x27,0x08,0x4E,0xE6);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIterator,0x50FB36CD,0x3063,0x4B73,0xBD,0xBB,0x95,0x80,0x87,0xF2,0xD8,0xBA);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAPIInformation,0x7BEA3C68,0x730D,0x4322,0xAF,0x34,0x8A,0x71,0x52,0xB5,0x32,0xA4);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowAttributes,0xCDA938DA,0x6479,0x40C6,0xB2,0xEC,0xA3,0x57,0x9B,0x3A,0xEE,0xCD);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowStatus,0x31C41656,0x4992,0x4396,0xBB,0xE9,0x5F,0x84,0x06,0xAA,0xB5,0xAF);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowSetting,0x86DD9174,0x27D3,0x4032,0xB2,0xAD,0x60,0x67,0xC3,0xBB,0x24,0x24);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlow,0xC5FC83C7,0x5B8E,0x42A7,0x9A,0x40,0x7C,0x06,0x59,0x55,0xD4,0xE1);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPFlowIterator,0xBD296AB2,0xA5C5,0x4153,0x88,0x8F,0xAA,0xB1,0xFD,0xBD,0x8A,0x5C);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput,0x5F227C95,0x39D7,0x46C7,0x8B,0x7D,0x9C,0x81,0x79,0x5F,0xBB,0xE4);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput,0x6A515F8A,0xFBCE,0x4853,0xB0,0xF7,0x2A,0x09,0xDB,0x1E,0xCA,0x0B);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkIPExtensions,0x46CF7903,0xA9FD,0x4D0B,0x8F,0xFC,0x01,0x03,0x72,0x2A,0xB4,0x42);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkHDMIInputEDID,0xABBBACBC,0x45BC,0x4665,0x9D,0x92,0xAC,0xE6,0xE5,0xA9,0x79,0x02);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInput,0x46C1332E,0x6FD9,0x472A,0x85,0x91,0xFE,0x59,0xC2,0x21,0x92,0xE1);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBuffer,0x81F03D70,0xDE13,0x4B17,0x87,0x3A,0xC8,0xAC,0x96,0x89,0xC6,0x82);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame,0x6502091C,0x615F,0x4F51,0xBA,0xF6,0x45,0xC4,0x25,0x6D,0xD5,0xB0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkMutableVideoFrame,0xCF9EB134,0x0374,0x4C5B,0x95,0xFA,0x1E,0xC1,0x48,0x19,0xFF,0x62);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame3DExtensions,0xD4DBE9C6,0xB4D2,0x49D3,0xAB,0xF2,0xB4,0xE8,0x6C,0x73,0x91,0xB0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameMetadataExtensions,0xE232A5B7,0x4DB4,0x44C9,0x91,0x52,0xF4,0x7C,0x12,0xE5,0xF0,0x51);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameMutableMetadataExtensions,0xCC198FC6,0x8298,0x4419,0x94,0x2D,0x83,0x57,0xEC,0x35,0x5E,0x58);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame,0xC9ADD3D2,0xBE52,0x488D,0xAB,0x2D,0x7F,0xDE,0xF7,0xAF,0x0C,0x95);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacket,0xF5C0D498,0x5CD3,0x4C77,0x97,0x73,0x8E,0xFA,0x20,0xBB,0x33,0x4B);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacketIterator,0x10F1AA88,0x54BE,0x42F7,0xB9,0xF8,0xEC,0x2F,0x5F,0x09,0x95,0x51);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillaryPackets,0x8A72D630,0x8070,0x4D05,0x8A,0x93,0xE6,0x0C,0x40,0xEE,0x08,0x8A);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillary,0x732E723C,0xD1A4,0x4E29,0x9E,0x8E,0x4A,0x88,0x79,0x7A,0x00,0x04);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderPacket,0xB693F36C,0x316E,0x4AF1,0xB6,0xC2,0xF3,0x89,0xA4,0xBC,0xA6,0x20);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderVideoPacket,0x4E7FD944,0xE8C7,0x4EAC,0xB8,0xC0,0x7B,0x77,0xF8,0x0F,0x5A,0xE0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderAudioPacket,0x49E8EDC8,0x693B,0x4E14,0x8E,0xF6,0x12,0xC6,0x58,0xF5,0xA0,0x7A);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkH265NALPacket,0x639C8E0B,0x68D5,0x4BDE,0xA6,0xD4,0x95,0xF3,0xAE,0xAF,0xF2,0xE7);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioInputPacket,0xE43D5870,0x2894,0x11DE,0x8C,0x30,0x08,0x00,0x20,0x0C,0x9A,0x66);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkScreenPreviewCallback,0xD4FA2345,0x9FBA,0x4497,0x95,0xC3,0xC0,0xC3,0xCE,0xD3,0xCD,0xA8);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkGLScreenPreviewHelper,0xCEB778E2,0xC202,0x4EC8,0x90,0x85,0x0C,0xD2,0x85,0xCC,0x55,0x22);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDX9ScreenPreviewHelper,0xF2DD78CA,0x2921,0x4AC2,0xB5,0xBC,0xBF,0xDC,0xC2,0x03,0x5A,0x1F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkWPFDX9ScreenPreviewHelper,0xC59346CD,0x9326,0x4266,0xAC,0x2D,0x5C,0x19,0x0F,0x57,0x99,0xEE);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotificationCallback,0xb002a1ec,0x070d,0x4288,0x82,0x89,0xbd,0x5d,0x36,0xe5,0xff,0x0d);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotification,0x1d70faac,0xfd27,0x4866,0x9d,0xe6,0x09,0x39,0xd1,0xe4,0xc7,0xf1);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileAttributes,0xF47551D7,0xAD22,0x47AF,0xBC,0xFD,0x6B,0xE8,0x8A,0xA8,0x79,0xD9);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileIterator,0x29E5A8C0,0x8BE4,0x46EB,0x93,0xAC,0x31,0xDA,0xAB,0x5B,0x7B,0xF2);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfile,0x16093466,0x674A,0x432B,0x9D,0xA0,0x1A,0xC2,0xC5,0xA8,0x24,0x1C);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileCallback,0xA4F9341E,0x97AA,0x4E04,0x89,0x35,0x15,0xF8,0x09,0x89,0x8C,0xEA);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileManager,0x30D41429,0x3998,0x4B6D,0x84,0xF8,0x78,0xC9,0x4A,0x79,0x7C,0x6E);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkStatistics,0x21CB2ED1,0x4429,0x42BE,0xAA,0xF3,0x22,0xA3,0xB1,0xDD,0x3A,0xE0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkStatus,0x2A04A635,0xED42,0x41EF,0x93,0x42,0x0E,0x11,0xF8,0xCF,0x6B,0x5E);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkKeyer,0x89AFCAF5,0x65F8,0x421E,0x98,0xF7,0x96,0xFE,0x5F,0x5B,0xFB,0xA3);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion,0x94C536D6,0xC821,0x42F5,0xA6,0x00,0xC6,0x66,0x29,0x95,0x51,0x01);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeviceNotificationCallback,0x4997053B,0x0ADF,0x4CC8,0xAC,0x70,0x7A,0x50,0xC4,0xBE,0x72,0x8F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDiscovery,0xCDBF631C,0xBC76,0x45FA,0xB4,0x4D,0xC5,0x50,0x59,0xBC,0x61,0x01);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator,0xBA6C6F44,0x6DA5,0x4DCE,0x94,0xAA,0xEE,0x2D,0x13,0x72,0xA6,0x76);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkAPIInformation,0x263CA19F,0xED09,0x482E,0x9F,0x9D,0x84,0x00,0x57,0x83,0xA2,0x37);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGLScreenPreviewHelper,0x1E332DAE,0x0D04,0x49EB,0xB8,0xA1,0xB6,0xE0,0x0B,0x2B,0x6B,0xD0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGL3ScreenPreviewHelper,0x166804E4,0x15EF,0x4BFD,0xB6,0x23,0xB5,0xBA,0x92,0x16,0x67,0xC5);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDX9ScreenPreviewHelper,0x0EB111ED,0xADA6,0x43A6,0x8B,0x16,0xCA,0x5D,0x27,0xEE,0xA1,0x5E);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkWPFDX9ScreenPreviewHelper,0x5E64496D,0x4BB2,0x45D5,0x9B,0x63,0xBF,0x1B,0x46,0x3B,0x18,0xAF);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion,0x771AD62D,0x671F,0x4442,0xAC,0x90,0xB0,0x70,0xC5,0x41,0x09,0x0A);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDiscovery,0x22FBFC33,0x8D07,0x495C,0xA5,0xBF,0xDA,0xB5,0xEA,0x9B,0x82,0xDB);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoFrameAncillaryPackets,0x6F47097E,0xB390,0x4650,0xBC,0xB6,0xC4,0xD5,0x2F,0xAA,0x16,0x43);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkStatus_v15_3_1,0x5F558200,0x4028,0x49BC,0xBE,0xAC,0xDB,0x3F,0xA4,0xA9,0x6E,0x46);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v15_3_1,0x912F634B,0x2D4E,0x40A4,0x8A,0xAB,0x8D,0x80,0xB7,0x3F,0x12,0x89);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBuffer_v15_3_1,0xCCB4B64A,0x5C86,0x4E02,0xB7,0x78,0x88,0x5D,0x35,0x27,0x09,0xFE);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocator_v15_3_1,0x3481A4DF,0x2B11,0x4E55,0xAC,0x61,0x83,0x6B,0x87,0x98,0x5E,0x9A);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoBufferAllocatorProvider_v15_3_1,0x08B80403,0xBFF2,0x49D0,0xB4,0x48,0x8C,0x90,0x8B,0x9E,0x9F,0xC9);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v15_3_1,0x4095DB82,0xE294,0x4B8C,0xAA,0xA8,0x3B,0x9E,0x80,0xC4,0x93,0x36);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v15_3_1,0x1A8077F1,0x9FE2,0x4533,0x81,0x47,0x22,0x94,0x30,0x5E,0x25,0x3F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion_v15_3_1,0xA48755D9,0x8BD5,0x4727,0xA1,0xE9,0x06,0x9F,0xDE,0xDB,0xA6,0xE9);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotification_v15_3_1,0xB85DF4C8,0xBDF5,0x47C1,0x80,0x64,0x28,0x16,0x2E,0xBD,0xD4,0xEB);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion_v15_3_1,0x89BA47BD,0x1FE2,0x4D76,0x9B,0xFE,0xDE,0x85,0x04,0x9C,0x49,0x87);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkProfileAttributes_v15_3_1,0x17D4BF8E,0x4911,0x473A,0x80,0xA0,0x73,0x1C,0xF6,0xFF,0x34,0x5B);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback_v14_2_1,0x20AA5225,0x1958,0x47CB,0x82,0x0B,0x80,0xA8,0xD5,0x21,0xA6,0xEE);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v14_2_1,0xC6FCE4C9,0xC4E4,0x4047,0x82,0xFB,0x5D,0x23,0x82,0x32,0xA9,0x02);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkMemoryAllocator_v14_2_1,0xB36EB6E7,0x9D29,0x4AA8,0x92,0xEF,0x84,0x3B,0x87,0xA2,0x89,0xE8);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v14_2_1,0xBE2D9020,0x461E,0x442F,0x84,0xB7,0xE9,0x49,0xCB,0x95,0x3B,0x9D);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v14_2_1,0xC21CDB6E,0xF414,0x46E4,0xA6,0x36,0x80,0xA5,0x66,0xE0,0xED,0x37);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInput_v14_2_1,0xF222551D,0x13DF,0x4FD8,0xB5,0x87,0x9D,0x4F,0x19,0xEC,0x12,0xC9);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame_v14_2_1,0x3F716FE0,0xF023,0x4111,0xBE,0x5D,0xEF,0x44,0x14,0xC0,0x5B,0x17);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkMutableVideoFrame_v14_2_1,0x69E2639F,0x40DA,0x4E19,0xB6,0xF2,0x20,0xAC,0xE8,0x15,0xC3,0x90);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame3DExtensions_v14_2_1,0xDA0F7E4A,0xEDC7,0x48A8,0x9C,0xDD,0x2D,0xB5,0x1C,0x72,0x9C,0xD7);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame_v14_2_1,0x05CFE374,0x537C,0x4094,0x9A,0x57,0x68,0x05,0x25,0x11,0x8F,0x44);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkScreenPreviewCallback_v14_2_1,0xB1D3F49A,0x85FE,0x4C5D,0x95,0xC8,0x0B,0x5D,0x5D,0xCC,0xD4,0x38);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkGLScreenPreviewHelper_v14_2_1,0x504E2209,0xCAC7,0x4C1A,0x9F,0xB4,0xC5,0xBB,0x62,0x74,0xD2,0x2F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkDX9ScreenPreviewHelper_v14_2_1,0x2094B522,0xD1A1,0x40C0,0x9A,0xC7,0x1C,0x01,0x22,0x18,0xEF,0x02);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkWPFDX9ScreenPreviewHelper_v14_2_1,0xAD8EC84A,0x7DDE,0x11E9,0x8F,0x9E,0x2A,0x86,0xE4,0x08,0x5A,0x59);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion_v14_2_1,0x3BBCB8A2,0xDA2C,0x42D9,0xB5,0xD8,0x88,0x08,0x36,0x44,0xE9,0x9A);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGLScreenPreviewHelper_v14_2_1,0xF63E77C7,0xB655,0x4A4A,0x9A,0xD0,0x3C,0xA8,0x5D,0x39,0x43,0x43);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGL3ScreenPreviewHelper_v14_2_1,0x00696A71,0xEBC7,0x491F,0xAC,0x02,0x18,0xD3,0x39,0x3F,0x33,0xF0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDX9ScreenPreviewHelper_v14_2_1,0xCC010023,0xE01D,0x4525,0x9D,0x59,0x80,0xC8,0xAB,0x3D,0xC7,0xA0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkWPFDX9ScreenPreviewHelper_v14_2_1,0xEF2A8478,0x7DDF,0x11E9,0x8F,0x9E,0x2A,0x86,0xE4,0x08,0x5A,0x59);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion_v14_2_1,0x7DBBBB11,0x5B7B,0x467D,0xAE,0xA4,0xCE,0xA4,0x68,0xFD,0x36,0x8C);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v11_5_1,0xDD04E5EC,0x7415,0x42AB,0xAE,0x4A,0xE8,0x0C,0x4D,0xFC,0x04,0x4A);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v11_5_1,0x9434C6E4,0xB15D,0x4B1C,0x97,0x9E,0x66,0x1E,0x3D,0xDC,0xB4,0xB9);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_11,0xEF90380B,0x4AE5,0x4346,0x90,0x77,0xE2,0x88,0xE1,0x49,0xF1,0x29);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAttributes_v10_11,0xABC11843,0xD966,0x44CB,0x96,0xE2,0xA1,0xCB,0x5D,0x31,0x35,0xC4);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkNotification_v10_11,0x0A1FB207,0xE215,0x441B,0x9B,0x19,0x6F,0xA1,0x57,0x59,0x46,0xC5);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v10_11,0xCC5C8A6E,0x3F2F,0x4B3A,0x87,0xEA,0xFD,0x78,0xAF,0x30,0x05,0x64);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v10_11,0xAF22762B,0xDFAC,0x4846,0xAA,0x79,0xFA,0x88,0x83,0x56,0x09,0x95);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderInput_v10_11,0x270587DA,0x6B7D,0x42E7,0xA1,0xF0,0x6D,0x85,0x3F,0x58,0x11,0x85);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator_v10_11,0x87D2693F,0x8D4A,0x45C7,0xB4,0x3F,0x10,0xAC,0xBA,0x25,0xE6,0x8F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDiscovery_v10_11,0x652615D4,0x26CD,0x4514,0xB1,0x61,0x2F,0xD5,0x07,0x2E,0xD0,0x08);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_9,0xCB71734A,0xFE37,0x4E8D,0x8E,0x13,0x80,0x21,0x33,0xA1,0xC3,0xF2);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CBMDStreamingDiscovery_v10_8,0x0CAA31F6,0x8A26,0x40B0,0x86,0xA4,0xBF,0x58,0xDC,0xCA,0x71,0x0C);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_4,0x1E69FCF6,0x4203,0x4936,0x80,0x76,0x2A,0x9F,0x4C,0xFD,0x50,0xCB);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v10_2,0xC679A35B,0x610C,0x4D09,0xB7,0x48,0x1D,0x04,0x78,0x10,0x0F,0xC0);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacket_v15_2,0xCC5BBF7E,0x029C,0x4D3B,0x91,0x58,0x60,0x00,0xEF,0x5E,0x36,0x70);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkAncillaryPacketIterator_v15_2,0x3FC8994B,0x88FB,0x4C17,0x96,0x8F,0x9A,0xAB,0x69,0xD9,0x64,0xA7);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillaryPackets_v15_2,0x6C186C0F,0x459E,0x41D8,0xAE,0xE2,0x48,0x12,0xD8,0x1A,0xEE,0x68);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoFrameAncillaryPackets_v15_2,0xF891AD29,0xD0C2,0x46E9,0xA9,0x26,0x4E,0x2D,0x0D,0xD8,0xCF,0xAD);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameMetadataExtensions_v11_5,0xD5973DC9,0x6432,0x46D0,0x8F,0x0B,0x24,0x96,0xF8,0xA1,0x23,0x8F);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v11_4,0x065A0F6C,0xC508,0x4D0D,0xB9,0x19,0xF5,0xEB,0x0E,0xBF,0xC9,0x6B);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v11_4,0x2A88CF76,0xF494,0x4216,0xA7,0xEF,0xDC,0x74,0xEE,0xB8,0x38,0x82);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator_v10_8,0x1F2E109A,0x8F4F,0x49E4,0x92,0x03,0x13,0x55,0x95,0xCB,0x6F,0xA5);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkDiscovery_v10_8,0x1073A05C,0xD885,0x47E9,0xB3,0xC6,0x12,0x9B,0x3F,0x9F,0x64,0x8B);
|
||||
|
||||
|
||||
MIDL_DEFINE_GUID(IID, IID_IDeckLinkEncoderConfiguration_v10_5,0x67455668,0x0848,0x45DF,0x8D,0x8E,0x35,0x0A,0x77,0xC9,0xA0,0x28);
|
||||
|
||||
#undef MIDL_DEFINE_GUID
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
146
src/video/DeckLinkDisplayMode.cpp
Normal file
146
src/video/DeckLinkDisplayMode.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
std::string NormalizeModeToken(const std::string& value)
|
||||
{
|
||||
std::string normalized;
|
||||
for (unsigned char ch : value)
|
||||
{
|
||||
if (std::isalnum(ch))
|
||||
normalized.push_back(static_cast<char>(std::tolower(ch)));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName)
|
||||
{
|
||||
VideoFormat videoMode;
|
||||
if (!ResolveConfiguredVideoFormat(videoFormat, frameRate, videoMode))
|
||||
return false;
|
||||
|
||||
displayMode = videoMode.displayMode;
|
||||
displayModeName = videoMode.displayName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode)
|
||||
{
|
||||
const std::string formatToken = NormalizeModeToken(videoFormat);
|
||||
const std::string frameToken = NormalizeModeToken(frameRate);
|
||||
const std::string combinedToken = formatToken + frameToken;
|
||||
|
||||
struct ModeOption
|
||||
{
|
||||
const char* token;
|
||||
BMDDisplayMode mode;
|
||||
const char* displayName;
|
||||
};
|
||||
|
||||
static const ModeOption options[] =
|
||||
{
|
||||
{ "720p50", bmdModeHD720p50, "720p50" },
|
||||
{ "hd720p50", bmdModeHD720p50, "720p50" },
|
||||
{ "720p5994", bmdModeHD720p5994, "720p59.94" },
|
||||
{ "hd720p5994", bmdModeHD720p5994, "720p59.94" },
|
||||
{ "720p60", bmdModeHD720p60, "720p60" },
|
||||
{ "hd720p60", bmdModeHD720p60, "720p60" },
|
||||
{ "1080i50", bmdModeHD1080i50, "1080i50" },
|
||||
{ "hd1080i50", bmdModeHD1080i50, "1080i50" },
|
||||
{ "1080i5994", bmdModeHD1080i5994, "1080i59.94" },
|
||||
{ "hd1080i5994", bmdModeHD1080i5994, "1080i59.94" },
|
||||
{ "1080i60", bmdModeHD1080i6000, "1080i60" },
|
||||
{ "hd1080i60", bmdModeHD1080i6000, "1080i60" },
|
||||
{ "1080p2398", bmdModeHD1080p2398, "1080p23.98" },
|
||||
{ "hd1080p2398", bmdModeHD1080p2398, "1080p23.98" },
|
||||
{ "1080p24", bmdModeHD1080p24, "1080p24" },
|
||||
{ "hd1080p24", bmdModeHD1080p24, "1080p24" },
|
||||
{ "1080p25", bmdModeHD1080p25, "1080p25" },
|
||||
{ "hd1080p25", bmdModeHD1080p25, "1080p25" },
|
||||
{ "1080p2997", bmdModeHD1080p2997, "1080p29.97" },
|
||||
{ "hd1080p2997", bmdModeHD1080p2997, "1080p29.97" },
|
||||
{ "1080p30", bmdModeHD1080p30, "1080p30" },
|
||||
{ "hd1080p30", bmdModeHD1080p30, "1080p30" },
|
||||
{ "1080p50", bmdModeHD1080p50, "1080p50" },
|
||||
{ "hd1080p50", bmdModeHD1080p50, "1080p50" },
|
||||
{ "1080p5994", bmdModeHD1080p5994, "1080p59.94" },
|
||||
{ "hd1080p5994", bmdModeHD1080p5994, "1080p59.94" },
|
||||
{ "1080p60", bmdModeHD1080p6000, "1080p60" },
|
||||
{ "hd1080p60", bmdModeHD1080p6000, "1080p60" },
|
||||
{ "2160p2398", bmdMode4K2160p2398, "2160p23.98" },
|
||||
{ "4k2160p2398", bmdMode4K2160p2398, "2160p23.98" },
|
||||
{ "2160p24", bmdMode4K2160p24, "2160p24" },
|
||||
{ "4k2160p24", bmdMode4K2160p24, "2160p24" },
|
||||
{ "2160p25", bmdMode4K2160p25, "2160p25" },
|
||||
{ "4k2160p25", bmdMode4K2160p25, "2160p25" },
|
||||
{ "2160p2997", bmdMode4K2160p2997, "2160p29.97" },
|
||||
{ "4k2160p2997", bmdMode4K2160p2997, "2160p29.97" },
|
||||
{ "2160p30", bmdMode4K2160p30, "2160p30" },
|
||||
{ "4k2160p30", bmdMode4K2160p30, "2160p30" },
|
||||
{ "2160p50", bmdMode4K2160p50, "2160p50" },
|
||||
{ "4k2160p50", bmdMode4K2160p50, "2160p50" },
|
||||
{ "2160p5994", bmdMode4K2160p5994, "2160p59.94" },
|
||||
{ "4k2160p5994", bmdMode4K2160p5994, "2160p59.94" },
|
||||
{ "2160p60", bmdMode4K2160p60, "2160p60" },
|
||||
{ "4k2160p60", bmdMode4K2160p60, "2160p60" }
|
||||
};
|
||||
|
||||
for (const ModeOption& option : options)
|
||||
{
|
||||
if (combinedToken == option.token || (frameToken.empty() && formatToken == option.token))
|
||||
{
|
||||
videoMode.displayMode = option.mode;
|
||||
videoMode.displayName = option.displayName;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveConfiguredVideoFormats(
|
||||
const std::string& inputVideoFormat,
|
||||
const std::string& inputFrameRate,
|
||||
const std::string& outputVideoFormat,
|
||||
const std::string& outputFrameRate,
|
||||
VideoFormatSelection& videoModes,
|
||||
std::string& error)
|
||||
{
|
||||
if (!ResolveConfiguredVideoFormat(inputVideoFormat, inputFrameRate, videoModes.input))
|
||||
{
|
||||
error = "Unsupported DeckLink inputVideoFormat/inputFrameRate in config/runtime-host.json: " +
|
||||
inputVideoFormat + " / " + inputFrameRate;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveConfiguredVideoFormat(outputVideoFormat, outputFrameRate, videoModes.output))
|
||||
{
|
||||
error = "Unsupported DeckLink outputVideoFormat/outputFrameRate in config/runtime-host.json: " +
|
||||
outputVideoFormat + " / " + outputFrameRate;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode)
|
||||
{
|
||||
if (!iterator || !foundMode)
|
||||
return false;
|
||||
|
||||
*foundMode = NULL;
|
||||
IDeckLinkDisplayMode* candidate = NULL;
|
||||
while (iterator->Next(&candidate) == S_OK)
|
||||
{
|
||||
if (candidate->GetDisplayMode() == targetMode)
|
||||
{
|
||||
*foundMode = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
candidate->Release();
|
||||
candidate = NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
47
src/video/DeckLinkDisplayMode.h
Normal file
47
src/video/DeckLinkDisplayMode.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct FrameSize
|
||||
{
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
|
||||
bool IsEmpty() const { return width == 0 || height == 0; }
|
||||
};
|
||||
|
||||
inline bool operator==(const FrameSize& left, const FrameSize& right)
|
||||
{
|
||||
return left.width == right.width && left.height == right.height;
|
||||
}
|
||||
|
||||
inline bool operator!=(const FrameSize& left, const FrameSize& right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
struct VideoFormat
|
||||
{
|
||||
BMDDisplayMode displayMode = bmdModeHD1080p5994;
|
||||
std::string displayName = "1080p59.94";
|
||||
};
|
||||
|
||||
struct VideoFormatSelection
|
||||
{
|
||||
VideoFormat input;
|
||||
VideoFormat output;
|
||||
};
|
||||
|
||||
std::string NormalizeModeToken(const std::string& value);
|
||||
bool ResolveConfiguredDisplayMode(const std::string& videoFormat, const std::string& frameRate, BMDDisplayMode& displayMode, std::string& displayModeName);
|
||||
bool ResolveConfiguredVideoFormat(const std::string& videoFormat, const std::string& frameRate, VideoFormat& videoMode);
|
||||
bool ResolveConfiguredVideoFormats(
|
||||
const std::string& inputVideoFormat,
|
||||
const std::string& inputFrameRate,
|
||||
const std::string& outputVideoFormat,
|
||||
const std::string& outputFrameRate,
|
||||
VideoFormatSelection& videoModes,
|
||||
std::string& error);
|
||||
bool FindDeckLinkDisplayMode(IDeckLinkDisplayModeIterator* iterator, BMDDisplayMode targetMode, IDeckLinkDisplayMode** foundMode);
|
||||
100
src/video/DeckLinkFrameTransfer.cpp
Normal file
100
src/video/DeckLinkFrameTransfer.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "DeckLinkFrameTransfer.h"
|
||||
|
||||
#include "DeckLinkSession.h"
|
||||
|
||||
////////////////////////////////////////////
|
||||
// DeckLink Capture Delegate Class
|
||||
////////////////////////////////////////////
|
||||
CaptureDelegate::CaptureDelegate(DeckLinkSession* pOwner) :
|
||||
m_pOwner(pOwner),
|
||||
mRefCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT CaptureDelegate::QueryInterface(REFIID, LPVOID* ppv)
|
||||
{
|
||||
*ppv = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
ULONG CaptureDelegate::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&mRefCount);
|
||||
}
|
||||
|
||||
ULONG CaptureDelegate::Release()
|
||||
{
|
||||
int newCount = InterlockedDecrement(&mRefCount);
|
||||
if (newCount == 0)
|
||||
delete this;
|
||||
return newCount;
|
||||
}
|
||||
|
||||
HRESULT CaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* inputFrame, IDeckLinkAudioInputPacket*)
|
||||
{
|
||||
if (!inputFrame)
|
||||
{
|
||||
// It's possible to receive a NULL inputFrame, but a valid audioPacket. Ignore audio-only frame.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool hasNoInputSource = (inputFrame->GetFlags() & bmdFrameHasNoInputSource) == bmdFrameHasNoInputSource;
|
||||
m_pOwner->HandleVideoInputFrame(inputFrame, hasNoInputSource);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT CaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// DeckLink Playout Delegate Class
|
||||
////////////////////////////////////////////
|
||||
PlayoutDelegate::PlayoutDelegate(DeckLinkSession* pOwner) :
|
||||
m_pOwner(pOwner),
|
||||
mRefCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT PlayoutDelegate::QueryInterface(REFIID, LPVOID* ppv)
|
||||
{
|
||||
*ppv = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
ULONG PlayoutDelegate::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&mRefCount);
|
||||
}
|
||||
|
||||
ULONG PlayoutDelegate::Release()
|
||||
{
|
||||
int newCount = InterlockedDecrement(&mRefCount);
|
||||
if (newCount == 0)
|
||||
delete this;
|
||||
return newCount;
|
||||
}
|
||||
|
||||
HRESULT PlayoutDelegate::ScheduledFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case bmdOutputFrameDisplayedLate:
|
||||
case bmdOutputFrameDropped:
|
||||
case bmdOutputFrameCompleted:
|
||||
case bmdOutputFrameFlushed:
|
||||
// Late/drop counts are recorded by VideoBackend; keep this callback lean.
|
||||
break;
|
||||
default:
|
||||
OutputDebugStringA("ScheduledFrameCompleted() frame did not complete: Unknown error\n");
|
||||
}
|
||||
|
||||
m_pOwner->HandlePlayoutFrameCompleted(completedFrame, result);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT PlayoutDelegate::ScheduledPlaybackHasStopped()
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
49
src/video/DeckLinkFrameTransfer.h
Normal file
49
src/video/DeckLinkFrameTransfer.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
|
||||
class DeckLinkSession;
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Capture Delegate Class
|
||||
////////////////////////////////////////////
|
||||
class CaptureDelegate : public IDeckLinkInputCallback
|
||||
{
|
||||
DeckLinkSession* m_pOwner;
|
||||
LONG mRefCount;
|
||||
|
||||
public:
|
||||
CaptureDelegate(DeckLinkSession* 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
|
||||
{
|
||||
DeckLinkSession* m_pOwner;
|
||||
LONG mRefCount;
|
||||
|
||||
public:
|
||||
PlayoutDelegate(DeckLinkSession* 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();
|
||||
};
|
||||
933
src/video/DeckLinkSession.cpp
Normal file
933
src/video/DeckLinkSession.cpp
Normal file
@@ -0,0 +1,933 @@
|
||||
#include "DeckLinkSession.h"
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int64_t kMinimumHealthyScheduleLeadFrames = 4;
|
||||
constexpr int64_t kProactiveScheduleLeadFloorFrames = 1;
|
||||
|
||||
class SystemMemoryDeckLinkVideoBuffer : public IDeckLinkVideoBuffer
|
||||
{
|
||||
public:
|
||||
SystemMemoryDeckLinkVideoBuffer(void* bytes, unsigned long long sizeBytes) :
|
||||
mBytes(bytes),
|
||||
mSizeBytes(sizeBytes),
|
||||
mRefCount(1)
|
||||
{
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID* ppv) override
|
||||
{
|
||||
if (ppv == nullptr)
|
||||
return E_POINTER;
|
||||
if (iid == IID_IUnknown || iid == IID_IDeckLinkVideoBuffer)
|
||||
{
|
||||
*ppv = static_cast<IDeckLinkVideoBuffer*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
*ppv = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef() override
|
||||
{
|
||||
return ++mRefCount;
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE Release() override
|
||||
{
|
||||
const ULONG refCount = --mRefCount;
|
||||
if (refCount == 0)
|
||||
delete this;
|
||||
return refCount;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBytes(void** buffer) override
|
||||
{
|
||||
if (buffer == nullptr)
|
||||
return E_POINTER;
|
||||
*buffer = mBytes;
|
||||
return mBytes != nullptr ? S_OK : E_FAIL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetSize(unsigned long long* bufferSize) override
|
||||
{
|
||||
if (bufferSize == nullptr)
|
||||
return E_POINTER;
|
||||
*bufferSize = mSizeBytes;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE StartAccess(BMDBufferAccessFlags) override
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE EndAccess(BMDBufferAccessFlags) override
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void* mBytes = nullptr;
|
||||
unsigned long long mSizeBytes = 0;
|
||||
std::atomic<ULONG> mRefCount;
|
||||
};
|
||||
|
||||
std::string BstrToUtf8(BSTR value)
|
||||
{
|
||||
if (value == nullptr)
|
||||
return std::string();
|
||||
|
||||
const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
|
||||
if (requiredBytes <= 1)
|
||||
return std::string();
|
||||
|
||||
std::vector<char> utf8Name(static_cast<std::size_t>(requiredBytes), '\0');
|
||||
if (WideCharToMultiByte(CP_UTF8, 0, value, -1, utf8Name.data(), requiredBytes, NULL, NULL) <= 0)
|
||||
return std::string();
|
||||
|
||||
return std::string(utf8Name.data());
|
||||
}
|
||||
|
||||
bool InputSupportsFormat(IDeckLinkInput* input, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
|
||||
{
|
||||
if (input == nullptr)
|
||||
return false;
|
||||
|
||||
BOOL supported = FALSE;
|
||||
BMDDisplayMode actualMode = bmdModeUnknown;
|
||||
const HRESULT result = input->DoesSupportVideoMode(
|
||||
bmdVideoConnectionUnspecified,
|
||||
displayMode,
|
||||
pixelFormat,
|
||||
bmdNoVideoInputConversion,
|
||||
bmdSupportedVideoModeDefault,
|
||||
&actualMode,
|
||||
&supported);
|
||||
return result == S_OK && supported != FALSE;
|
||||
}
|
||||
|
||||
bool OutputSupportsFormat(IDeckLinkOutput* output, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat)
|
||||
{
|
||||
if (output == nullptr)
|
||||
return false;
|
||||
|
||||
BOOL supported = FALSE;
|
||||
BMDDisplayMode actualMode = bmdModeUnknown;
|
||||
const HRESULT result = output->DoesSupportVideoMode(
|
||||
bmdVideoConnectionUnspecified,
|
||||
displayMode,
|
||||
pixelFormat,
|
||||
bmdNoVideoOutputConversion,
|
||||
bmdSupportedVideoModeDefault,
|
||||
&actualMode,
|
||||
&supported);
|
||||
return result == S_OK && supported != FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
DeckLinkSession::~DeckLinkSession()
|
||||
{
|
||||
ReleaseResources();
|
||||
}
|
||||
|
||||
void DeckLinkSession::ReleaseResources()
|
||||
{
|
||||
if (input != nullptr)
|
||||
input->SetCallback(nullptr);
|
||||
captureDelegate.Release();
|
||||
input.Release();
|
||||
|
||||
if (output != nullptr)
|
||||
output->SetScheduledFrameCompletionCallback(nullptr);
|
||||
|
||||
if (keyer != nullptr)
|
||||
{
|
||||
keyer->Disable();
|
||||
mState.externalKeyingActive = false;
|
||||
}
|
||||
keyer.Release();
|
||||
|
||||
playoutDelegate.Release();
|
||||
outputVideoFrameQueue.clear();
|
||||
output.Release();
|
||||
}
|
||||
|
||||
bool DeckLinkSession::DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error)
|
||||
{
|
||||
CComPtr<IDeckLinkIterator> deckLinkIterator;
|
||||
CComPtr<IDeckLinkDisplayMode> inputMode;
|
||||
CComPtr<IDeckLinkDisplayMode> outputMode;
|
||||
|
||||
mState.inputDisplayModeName = videoModes.input.displayName;
|
||||
mState.outputDisplayModeName = videoModes.output.displayName;
|
||||
|
||||
HRESULT result = CoCreateInstance(CLSID_CDeckLinkIterator, nullptr, CLSCTX_ALL, IID_IDeckLinkIterator, reinterpret_cast<void**>(&deckLinkIterator));
|
||||
if (FAILED(result))
|
||||
{
|
||||
error = "Please install the Blackmagic DeckLink drivers to use the features of this application.";
|
||||
return false;
|
||||
}
|
||||
|
||||
CComPtr<IDeckLink> deckLink;
|
||||
while (deckLinkIterator->Next(&deckLink) == S_OK)
|
||||
{
|
||||
int64_t duplexMode;
|
||||
bool deviceSupportsInternalKeying = false;
|
||||
bool deviceSupportsExternalKeying = false;
|
||||
std::string modelName;
|
||||
CComPtr<IDeckLinkProfileAttributes> deckLinkAttributes;
|
||||
|
||||
if (deckLink->QueryInterface(IID_IDeckLinkProfileAttributes, (void**)&deckLinkAttributes) != S_OK)
|
||||
{
|
||||
printf("Could not obtain the IDeckLinkProfileAttributes interface\n");
|
||||
deckLink.Release();
|
||||
continue;
|
||||
}
|
||||
|
||||
result = deckLinkAttributes->GetInt(BMDDeckLinkDuplex, &duplexMode);
|
||||
BOOL attributeFlag = FALSE;
|
||||
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &attributeFlag) == S_OK)
|
||||
deviceSupportsInternalKeying = (attributeFlag != FALSE);
|
||||
attributeFlag = FALSE;
|
||||
if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &attributeFlag) == S_OK)
|
||||
deviceSupportsExternalKeying = (attributeFlag != FALSE);
|
||||
CComBSTR modelNameBstr;
|
||||
if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK)
|
||||
modelName = BstrToUtf8(modelNameBstr);
|
||||
|
||||
if (result != S_OK || duplexMode == bmdDuplexInactive)
|
||||
{
|
||||
deckLink.Release();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool inputUsed = false;
|
||||
if (!input && deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&input) == S_OK)
|
||||
inputUsed = true;
|
||||
|
||||
if (!output && (!inputUsed || (duplexMode == bmdDuplexFull)))
|
||||
{
|
||||
if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&output) != S_OK)
|
||||
output.Release();
|
||||
else
|
||||
{
|
||||
mState.outputModelName = modelName;
|
||||
mState.supportsInternalKeying = deviceSupportsInternalKeying;
|
||||
mState.supportsExternalKeying = deviceSupportsExternalKeying;
|
||||
}
|
||||
}
|
||||
|
||||
deckLink.Release();
|
||||
|
||||
if (output && input)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!output)
|
||||
{
|
||||
error = "Expected an Output DeckLink device";
|
||||
ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
CComPtr<IDeckLinkDisplayModeIterator> inputDisplayModeIterator;
|
||||
if (input && input->GetDisplayModeIterator(&inputDisplayModeIterator) != S_OK)
|
||||
{
|
||||
error = "Cannot get input Display Mode Iterator.";
|
||||
ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input && !FindDeckLinkDisplayMode(inputDisplayModeIterator, videoModes.input.displayMode, &inputMode))
|
||||
{
|
||||
error = "Cannot get specified input BMDDisplayMode for configured mode: " + videoModes.input.displayName;
|
||||
ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
inputDisplayModeIterator.Release();
|
||||
|
||||
CComPtr<IDeckLinkDisplayModeIterator> outputDisplayModeIterator;
|
||||
if (output->GetDisplayModeIterator(&outputDisplayModeIterator) != S_OK)
|
||||
{
|
||||
error = "Cannot get output Display Mode Iterator.";
|
||||
ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FindDeckLinkDisplayMode(outputDisplayModeIterator, videoModes.output.displayMode, &outputMode))
|
||||
{
|
||||
error = "Cannot get specified output BMDDisplayMode for configured mode: " + videoModes.output.displayName;
|
||||
ReleaseResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
mState.outputFrameSize = { static_cast<unsigned>(outputMode->GetWidth()), static_cast<unsigned>(outputMode->GetHeight()) };
|
||||
mState.inputFrameSize = inputMode
|
||||
? FrameSize{ static_cast<unsigned>(inputMode->GetWidth()), static_cast<unsigned>(inputMode->GetHeight()) }
|
||||
: mState.outputFrameSize;
|
||||
if (!input)
|
||||
mState.inputDisplayModeName = "No input - black frame";
|
||||
BMDTimeValue frameDuration = 0;
|
||||
BMDTimeScale frameTimescale = 0;
|
||||
outputMode->GetFrameRate(&frameDuration, &frameTimescale);
|
||||
mScheduler.Configure(frameDuration, frameTimescale, mPlayoutPolicy);
|
||||
mState.frameBudgetMilliseconds = mScheduler.FrameBudgetMilliseconds();
|
||||
|
||||
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||
mState.outputFrameRowBytes = mState.outputFrameSize.width * 4u;
|
||||
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
|
||||
mState.outputPackTextureWidth = mState.outputFrameSize.width;
|
||||
mState.hasInputDevice = input != nullptr;
|
||||
mState.hasInputSource = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error)
|
||||
{
|
||||
if (!output)
|
||||
{
|
||||
error = "Expected an Output DeckLink device";
|
||||
return false;
|
||||
}
|
||||
|
||||
mState.formatStatusMessage.clear();
|
||||
|
||||
const bool inputTenBitSupported = input != nullptr && InputSupportsFormat(input, videoModes.input.displayMode, bmdFormat10BitYUV);
|
||||
mState.inputPixelFormat = input != nullptr ? ChoosePreferredVideoIOFormat(inputTenBitSupported) : VideoIOPixelFormat::Uyvy8;
|
||||
if (input != nullptr && !inputTenBitSupported)
|
||||
mState.formatStatusMessage += "DeckLink input does not report 10-bit YUV support for the configured mode; using 8-bit capture. ";
|
||||
|
||||
const bool outputTenBitSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUV);
|
||||
const bool outputTenBitYuvaSupported = OutputSupportsFormat(output, videoModes.output.displayMode, bmdFormat10BitYUVA);
|
||||
mState.outputPixelFormat = outputAlphaRequired
|
||||
? (outputTenBitYuvaSupported ? VideoIOPixelFormat::Yuva10 : VideoIOPixelFormat::Bgra8)
|
||||
: (outputTenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Bgra8);
|
||||
if (outputAlphaRequired && outputTenBitYuvaSupported)
|
||||
mState.formatStatusMessage += "External keying requires alpha; using 10-bit YUVA output. ";
|
||||
else if (outputAlphaRequired)
|
||||
mState.formatStatusMessage += "External keying requires alpha, but DeckLink output does not report 10-bit YUVA support for the configured mode; using 8-bit BGRA output. ";
|
||||
else if (!outputTenBitSupported)
|
||||
mState.formatStatusMessage += "DeckLink output does not report 10-bit YUV support for the configured mode; using 8-bit BGRA output. ";
|
||||
|
||||
int deckLinkOutputRowBytes = 0;
|
||||
if (output->RowBytesForPixelFormat(DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat), mState.outputFrameSize.width, &deckLinkOutputRowBytes) != S_OK)
|
||||
{
|
||||
error = "DeckLink output setup failed while calculating output row bytes.";
|
||||
return false;
|
||||
}
|
||||
mState.outputFrameRowBytes = static_cast<unsigned>(deckLinkOutputRowBytes);
|
||||
mState.outputPackTextureWidth = OutputIsTenBit()
|
||||
? PackedTextureWidthFromRowBytes(mState.outputFrameRowBytes)
|
||||
: mState.outputFrameSize.width;
|
||||
|
||||
if (InputIsTenBit())
|
||||
{
|
||||
int deckLinkInputRowBytes = 0;
|
||||
if (output->RowBytesForPixelFormat(bmdFormat10BitYUV, mState.inputFrameSize.width, &deckLinkInputRowBytes) == S_OK)
|
||||
mState.inputFrameRowBytes = static_cast<unsigned>(deckLinkInputRowBytes);
|
||||
else
|
||||
mState.inputFrameRowBytes = MinimumV210RowBytes(mState.inputFrameSize.width);
|
||||
}
|
||||
else
|
||||
{
|
||||
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||
}
|
||||
mState.captureTextureWidth = InputIsTenBit()
|
||||
? PackedTextureWidthFromRowBytes(mState.inputFrameRowBytes)
|
||||
: mState.inputFrameSize.width / 2u;
|
||||
|
||||
std::ostringstream status;
|
||||
status << "DeckLink formats: capture " << (input ? VideoIOPixelFormatName(mState.inputPixelFormat) : "none")
|
||||
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat) << ".";
|
||||
if (!mState.formatStatusMessage.empty())
|
||||
status << " " << mState.formatStatusMessage;
|
||||
mState.formatStatusMessage = status.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error)
|
||||
{
|
||||
mInputFrameCallback = std::move(callback);
|
||||
|
||||
if (!input)
|
||||
{
|
||||
mState.hasInputSource = false;
|
||||
mState.inputDisplayModeName = "No input - black frame";
|
||||
return true;
|
||||
}
|
||||
|
||||
const BMDPixelFormat deckLinkInputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.inputPixelFormat);
|
||||
if (input->EnableVideoInput(inputVideoMode.displayMode, deckLinkInputPixelFormat, bmdVideoInputFlagDefault) != S_OK)
|
||||
{
|
||||
if (mState.inputPixelFormat == VideoIOPixelFormat::V210)
|
||||
{
|
||||
OutputDebugStringA("DeckLink 10-bit input could not be enabled; falling back to 8-bit capture.\n");
|
||||
mState.inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
mState.inputFrameRowBytes = mState.inputFrameSize.width * 2u;
|
||||
mState.captureTextureWidth = mState.inputFrameSize.width / 2u;
|
||||
if (input->EnableVideoInput(inputVideoMode.displayMode, bmdFormat8BitYUV, bmdVideoInputFlagDefault) == S_OK)
|
||||
{
|
||||
std::ostringstream status;
|
||||
status << "DeckLink formats: capture " << VideoIOPixelFormatName(mState.inputPixelFormat)
|
||||
<< ", output " << VideoIOPixelFormatName(mState.outputPixelFormat)
|
||||
<< ". DeckLink 10-bit input enable failed; using 8-bit capture.";
|
||||
mState.formatStatusMessage = status.str();
|
||||
goto input_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
OutputDebugStringA("DeckLink input could not be enabled; continuing in output-only black-frame mode.\n");
|
||||
input.Release();
|
||||
mState.hasInputDevice = false;
|
||||
mState.hasInputSource = false;
|
||||
mState.inputDisplayModeName = "No input - black frame";
|
||||
return true;
|
||||
}
|
||||
|
||||
input_enabled:
|
||||
captureDelegate.Attach(new (std::nothrow) CaptureDelegate(this));
|
||||
if (captureDelegate == nullptr)
|
||||
{
|
||||
error = "DeckLink input setup failed while creating the capture callback.";
|
||||
return false;
|
||||
}
|
||||
if (input->SetCallback(captureDelegate) != S_OK)
|
||||
{
|
||||
error = "DeckLink input setup failed while installing the capture callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error)
|
||||
{
|
||||
mOutputFrameCallback = std::move(callback);
|
||||
|
||||
if (output->EnableVideoOutput(outputVideoMode.displayMode, bmdVideoOutputFlagDefault) != S_OK)
|
||||
{
|
||||
error = "DeckLink output setup failed while enabling video output.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output->QueryInterface(IID_IDeckLinkKeyer, (void**)&keyer) == S_OK && keyer != NULL)
|
||||
mState.keyerInterfaceAvailable = true;
|
||||
|
||||
if (externalKeyingEnabled)
|
||||
{
|
||||
if (!mState.supportsExternalKeying)
|
||||
{
|
||||
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not report external keying support.";
|
||||
}
|
||||
else if (!mState.keyerInterfaceAvailable)
|
||||
{
|
||||
mState.statusMessage = "External keying was requested, but the selected DeckLink output does not expose the IDeckLinkKeyer interface.";
|
||||
}
|
||||
else if (keyer->Enable(TRUE) != S_OK || keyer->SetLevel(255) != S_OK)
|
||||
{
|
||||
mState.statusMessage = "External keying was requested, but enabling the DeckLink keyer failed.";
|
||||
}
|
||||
else
|
||||
{
|
||||
mState.externalKeyingActive = true;
|
||||
mState.statusMessage = "External keying is active on the selected DeckLink output.";
|
||||
}
|
||||
}
|
||||
else if (mState.supportsExternalKeying)
|
||||
{
|
||||
mState.statusMessage = "Selected DeckLink output supports external keying. Set enableExternalKeying to true in runtime-host.json to request it.";
|
||||
}
|
||||
|
||||
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||
mPlayoutPolicy = policy;
|
||||
for (unsigned i = 0; i < policy.outputFramePoolSize; i++)
|
||||
{
|
||||
CComPtr<IDeckLinkMutableVideoFrame> outputFrame;
|
||||
|
||||
const BMDPixelFormat deckLinkOutputPixelFormat = DeckLinkPixelFormatForVideoIO(mState.outputPixelFormat);
|
||||
if (output->CreateVideoFrame(mState.outputFrameSize.width, mState.outputFrameSize.height, mState.outputFrameRowBytes, deckLinkOutputPixelFormat, bmdFrameFlagFlipVertical, &outputFrame) != S_OK)
|
||||
{
|
||||
error = "DeckLink output setup failed while creating an output video frame.";
|
||||
return false;
|
||||
}
|
||||
|
||||
outputVideoFrameQueue.push_back(outputFrame);
|
||||
}
|
||||
|
||||
playoutDelegate.Attach(new (std::nothrow) PlayoutDelegate(this));
|
||||
if (playoutDelegate == nullptr)
|
||||
{
|
||||
error = "DeckLink output setup failed while creating the playout callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output->SetScheduledFrameCompletionCallback(playoutDelegate) != S_OK)
|
||||
{
|
||||
error = "DeckLink output setup failed while installing the scheduled-frame callback.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mState.formatStatusMessage.empty())
|
||||
mState.statusMessage = mState.statusMessage.empty() ? mState.formatStatusMessage : mState.formatStatusMessage + " " + mState.statusMessage;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
double DeckLinkSession::FrameBudgetMilliseconds() const
|
||||
{
|
||||
return mScheduler.FrameBudgetMilliseconds();
|
||||
}
|
||||
|
||||
bool DeckLinkSession::AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame)
|
||||
{
|
||||
if (outputVideoFrameQueue.empty())
|
||||
return false;
|
||||
|
||||
outputVideoFrame = outputVideoFrameQueue.front();
|
||||
outputVideoFrameQueue.pop_front();
|
||||
return outputVideoFrame != nullptr;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame)
|
||||
{
|
||||
if (outputVideoFrame == nullptr)
|
||||
return false;
|
||||
|
||||
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
|
||||
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
||||
return false;
|
||||
|
||||
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
||||
return false;
|
||||
|
||||
void* pFrame = nullptr;
|
||||
outputVideoFrameBuffer->GetBytes(&pFrame);
|
||||
|
||||
frame.bytes = pFrame;
|
||||
frame.rowBytes = outputVideoFrame->GetRowBytes();
|
||||
frame.width = mState.outputFrameSize.width;
|
||||
frame.height = mState.outputFrameSize.height;
|
||||
frame.pixelFormat = mState.outputPixelFormat;
|
||||
outputVideoFrame->AddRef();
|
||||
frame.nativeFrame = outputVideoFrame;
|
||||
frame.nativeBuffer = outputVideoFrameBuffer.Detach();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
||||
{
|
||||
if (outputVideoFrame == nullptr || output == nullptr)
|
||||
{
|
||||
++mState.deckLinkScheduleFailureCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mScheduleRealignmentPending)
|
||||
{
|
||||
RealignScheduleCursorToPlayback();
|
||||
mScheduleRealignmentPending = false;
|
||||
}
|
||||
|
||||
UpdateScheduleLeadTelemetry();
|
||||
MaybeRealignScheduleCursorForLowLead();
|
||||
const VideoIOScheduleTime scheduleTime = mScheduler.NextScheduleTime();
|
||||
const auto scheduleStart = std::chrono::steady_clock::now();
|
||||
const HRESULT result = output->ScheduleVideoFrame(outputVideoFrame, scheduleTime.streamTime, scheduleTime.duration, scheduleTime.timeScale);
|
||||
const auto scheduleEnd = std::chrono::steady_clock::now();
|
||||
mState.deckLinkScheduleCallMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(scheduleEnd - scheduleStart).count();
|
||||
if (result != S_OK)
|
||||
++mState.deckLinkScheduleFailureCount;
|
||||
RefreshBufferedVideoFrameCount();
|
||||
return result == S_OK;
|
||||
}
|
||||
|
||||
void DeckLinkSession::UpdateScheduleLeadTelemetry()
|
||||
{
|
||||
if (output == nullptr)
|
||||
{
|
||||
mState.deckLinkScheduleLeadAvailable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
BMDTimeValue streamTime = 0;
|
||||
double playbackSpeed = 0.0;
|
||||
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) != S_OK || playbackSpeed <= 0.0)
|
||||
{
|
||||
mState.deckLinkScheduleLeadAvailable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t playbackFrameIndex = streamTime >= 0 && mScheduler.FrameDuration() > 0
|
||||
? static_cast<uint64_t>(streamTime / mScheduler.FrameDuration())
|
||||
: 0;
|
||||
const uint64_t nextScheduleFrameIndex = mScheduler.ScheduledFrameIndex();
|
||||
mState.deckLinkScheduleLeadAvailable = true;
|
||||
mState.deckLinkPlaybackStreamTime = streamTime;
|
||||
mState.deckLinkPlaybackFrameIndex = playbackFrameIndex;
|
||||
mState.deckLinkNextScheduleFrameIndex = nextScheduleFrameIndex;
|
||||
mState.deckLinkScheduleLeadFrames = static_cast<int64_t>(nextScheduleFrameIndex) - static_cast<int64_t>(playbackFrameIndex);
|
||||
}
|
||||
|
||||
void DeckLinkSession::MaybeRealignScheduleCursorForLowLead()
|
||||
{
|
||||
if (!mState.deckLinkScheduleLeadAvailable)
|
||||
return;
|
||||
|
||||
if (mState.deckLinkScheduleLeadFrames >= kMinimumHealthyScheduleLeadFrames)
|
||||
{
|
||||
mProactiveScheduleRealignmentArmed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mProactiveScheduleRealignmentArmed || mState.deckLinkScheduleLeadFrames > kProactiveScheduleLeadFloorFrames)
|
||||
return;
|
||||
|
||||
RealignScheduleCursorToPlayback();
|
||||
mProactiveScheduleRealignmentArmed = false;
|
||||
}
|
||||
|
||||
void DeckLinkSession::RealignScheduleCursorToPlayback()
|
||||
{
|
||||
if (output == nullptr)
|
||||
return;
|
||||
|
||||
BMDTimeValue streamTime = 0;
|
||||
double playbackSpeed = 0.0;
|
||||
if (output->GetScheduledStreamTime(mScheduler.TimeScale(), &streamTime, &playbackSpeed) != S_OK || playbackSpeed <= 0.0)
|
||||
return;
|
||||
|
||||
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||
mScheduler.AlignNextScheduleTimeToPlayback(streamTime, policy.targetPrerollFrames);
|
||||
++mState.deckLinkScheduleRealignmentCount;
|
||||
UpdateScheduleLeadTelemetry();
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame)
|
||||
{
|
||||
if (output == nullptr || frame.bytes == nullptr || frame.rowBytes <= 0 || frame.height == 0)
|
||||
return false;
|
||||
|
||||
CComPtr<IDeckLinkVideoBuffer> videoBuffer;
|
||||
videoBuffer.Attach(new (std::nothrow) SystemMemoryDeckLinkVideoBuffer(
|
||||
frame.bytes,
|
||||
static_cast<unsigned long long>(frame.rowBytes) * static_cast<unsigned long long>(frame.height)));
|
||||
if (videoBuffer == nullptr)
|
||||
return false;
|
||||
|
||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
||||
const BMDPixelFormat pixelFormat = DeckLinkPixelFormatForVideoIO(frame.pixelFormat);
|
||||
if (output->CreateVideoFrameWithBuffer(
|
||||
frame.width,
|
||||
frame.height,
|
||||
frame.rowBytes,
|
||||
pixelFormat,
|
||||
bmdFrameFlagFlipVertical,
|
||||
videoBuffer,
|
||||
&outputVideoFrame) != S_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IDeckLinkVideoFrame* scheduledFrame = outputVideoFrame;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mScheduledSystemFrameMutex);
|
||||
mScheduledSystemFrameBuffers[scheduledFrame] = frame.bytes;
|
||||
}
|
||||
|
||||
if (ScheduleFrame(outputVideoFrame))
|
||||
return true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mScheduledSystemFrameMutex);
|
||||
mScheduledSystemFrameBuffers.erase(scheduledFrame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame)
|
||||
{
|
||||
if (outputVideoFrame == nullptr)
|
||||
return false;
|
||||
|
||||
CComPtr<IDeckLinkVideoBuffer> outputVideoFrameBuffer;
|
||||
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
|
||||
return false;
|
||||
|
||||
if (outputVideoFrameBuffer->StartAccess(bmdBufferAccessWrite) != S_OK)
|
||||
return false;
|
||||
|
||||
void* pFrame = nullptr;
|
||||
outputVideoFrameBuffer->GetBytes((void**)&pFrame);
|
||||
memset(pFrame, 0, outputVideoFrame->GetRowBytes() * mState.outputFrameSize.height);
|
||||
|
||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||
return ScheduleFrame(outputVideoFrame);
|
||||
}
|
||||
|
||||
void DeckLinkSession::RefreshBufferedVideoFrameCount()
|
||||
{
|
||||
if (output == nullptr)
|
||||
{
|
||||
mState.actualDeckLinkBufferedFramesAvailable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int bufferedFrameCount = 0;
|
||||
if (output->GetBufferedVideoFrameCount(&bufferedFrameCount) == S_OK)
|
||||
{
|
||||
mState.actualDeckLinkBufferedFrames = bufferedFrameCount;
|
||||
mState.actualDeckLinkBufferedFramesAvailable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
mState.actualDeckLinkBufferedFramesAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DeckLinkSession::BeginOutputFrame(VideoIOOutputFrame& frame)
|
||||
{
|
||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
||||
return AcquireNextOutputVideoFrame(outputVideoFrame) && PopulateOutputFrame(outputVideoFrame, frame);
|
||||
}
|
||||
|
||||
void DeckLinkSession::EndOutputFrame(VideoIOOutputFrame& frame)
|
||||
{
|
||||
IDeckLinkVideoBuffer* outputVideoFrameBuffer = static_cast<IDeckLinkVideoBuffer*>(frame.nativeBuffer);
|
||||
if (outputVideoFrameBuffer != nullptr)
|
||||
{
|
||||
outputVideoFrameBuffer->EndAccess(bmdBufferAccessWrite);
|
||||
outputVideoFrameBuffer->Release();
|
||||
}
|
||||
frame.nativeBuffer = nullptr;
|
||||
frame.bytes = nullptr;
|
||||
}
|
||||
|
||||
VideoPlayoutRecoveryDecision DeckLinkSession::AccountForCompletionResult(VideoIOCompletionResult completionResult, uint64_t readyQueueDepth)
|
||||
{
|
||||
return mScheduler.AccountForCompletionResult(completionResult, readyQueueDepth);
|
||||
}
|
||||
|
||||
bool DeckLinkSession::ScheduleOutputFrame(const VideoIOOutputFrame& frame)
|
||||
{
|
||||
if (frame.nativeFrame == nullptr)
|
||||
return ScheduleSystemMemoryFrame(frame);
|
||||
|
||||
IDeckLinkMutableVideoFrame* outputVideoFrame = static_cast<IDeckLinkMutableVideoFrame*>(frame.nativeFrame);
|
||||
const bool scheduled = ScheduleFrame(outputVideoFrame);
|
||||
if (outputVideoFrame != nullptr)
|
||||
outputVideoFrame->Release();
|
||||
return scheduled;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::PrepareOutputSchedule()
|
||||
{
|
||||
mScheduler.Reset();
|
||||
RefreshBufferedVideoFrameCount();
|
||||
return output != nullptr;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::StartInputStreams()
|
||||
{
|
||||
if (!input)
|
||||
return true;
|
||||
|
||||
if (input->StartStreams() != S_OK)
|
||||
{
|
||||
MessageBoxA(NULL, "Could not start the DeckLink input stream.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::StartScheduledPlayback()
|
||||
{
|
||||
if (!output)
|
||||
{
|
||||
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output->StartScheduledPlayback(0, mScheduler.TimeScale(), 1.0) != S_OK)
|
||||
{
|
||||
MessageBoxA(NULL, "Could not start DeckLink scheduled playback.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
RefreshBufferedVideoFrameCount();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckLinkSession::Start()
|
||||
{
|
||||
if (!output)
|
||||
{
|
||||
MessageBoxA(NULL, "Cannot start playout because no DeckLink output device is available.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
if (outputVideoFrameQueue.empty())
|
||||
{
|
||||
MessageBoxA(NULL, "Cannot start playout because the output frame queue is empty.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
const VideoPlayoutPolicy policy = NormalizeVideoPlayoutPolicy(mPlayoutPolicy);
|
||||
mPlayoutPolicy = policy;
|
||||
if (!PrepareOutputSchedule())
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < policy.targetPrerollFrames; i++)
|
||||
{
|
||||
CComPtr<IDeckLinkMutableVideoFrame> outputVideoFrame;
|
||||
if (!AcquireNextOutputVideoFrame(outputVideoFrame))
|
||||
{
|
||||
MessageBoxA(NULL, "Could not acquire a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
if (!ScheduleBlackFrame(outputVideoFrame))
|
||||
{
|
||||
MessageBoxA(NULL, "Could not schedule a preroll output frame.", "DeckLink start failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return StartInputStreams() && StartScheduledPlayback();
|
||||
}
|
||||
|
||||
bool DeckLinkSession::Stop()
|
||||
{
|
||||
if (keyer != nullptr)
|
||||
{
|
||||
keyer->Disable();
|
||||
mState.externalKeyingActive = false;
|
||||
}
|
||||
|
||||
if (input)
|
||||
{
|
||||
input->StopStreams();
|
||||
input->DisableVideoInput();
|
||||
}
|
||||
|
||||
if (output)
|
||||
{
|
||||
output->StopScheduledPlayback(0, NULL, 0);
|
||||
output->DisableVideoOutput();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckLinkSession::HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
|
||||
{
|
||||
mState.hasInputSource = !hasNoInputSource;
|
||||
if (hasNoInputSource || mInputFrameCallback == nullptr)
|
||||
{
|
||||
VideoIOFrame frame;
|
||||
frame.width = mState.inputFrameSize.width;
|
||||
frame.height = mState.inputFrameSize.height;
|
||||
frame.pixelFormat = mState.inputPixelFormat;
|
||||
frame.hasNoInputSource = hasNoInputSource;
|
||||
if (mInputFrameCallback)
|
||||
mInputFrameCallback(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
CComPtr<IDeckLinkVideoBuffer> inputFrameBuffer;
|
||||
void* videoPixels = nullptr;
|
||||
if (inputFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&inputFrameBuffer) != S_OK)
|
||||
return;
|
||||
if (inputFrameBuffer->StartAccess(bmdBufferAccessRead) != S_OK)
|
||||
return;
|
||||
|
||||
inputFrameBuffer->GetBytes(&videoPixels);
|
||||
|
||||
VideoIOFrame frame;
|
||||
frame.bytes = videoPixels;
|
||||
frame.rowBytes = inputFrame->GetRowBytes();
|
||||
frame.width = static_cast<unsigned>(inputFrame->GetWidth());
|
||||
frame.height = static_cast<unsigned>(inputFrame->GetHeight());
|
||||
frame.pixelFormat = mState.inputPixelFormat;
|
||||
frame.hasNoInputSource = hasNoInputSource;
|
||||
mInputFrameCallback(frame);
|
||||
|
||||
inputFrameBuffer->EndAccess(bmdBufferAccessRead);
|
||||
}
|
||||
|
||||
void DeckLinkSession::HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult)
|
||||
{
|
||||
RefreshBufferedVideoFrameCount();
|
||||
|
||||
void* completedSystemBuffer = nullptr;
|
||||
if (completedFrame != nullptr)
|
||||
{
|
||||
bool externalSystemFrame = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mScheduledSystemFrameMutex);
|
||||
auto externalFrame = mScheduledSystemFrameBuffers.find(completedFrame);
|
||||
if (externalFrame != mScheduledSystemFrameBuffers.end())
|
||||
{
|
||||
completedSystemBuffer = externalFrame->second;
|
||||
mScheduledSystemFrameBuffers.erase(externalFrame);
|
||||
externalSystemFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!externalSystemFrame)
|
||||
{
|
||||
CComPtr<IDeckLinkMutableVideoFrame> reusableFrame;
|
||||
if (completedFrame->QueryInterface(IID_IDeckLinkMutableVideoFrame, reinterpret_cast<void**>(&reusableFrame)) == S_OK &&
|
||||
reusableFrame != nullptr)
|
||||
{
|
||||
outputVideoFrameQueue.push_back(reusableFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mOutputFrameCallback)
|
||||
return;
|
||||
|
||||
VideoIOCompletion completion;
|
||||
completion.result = TranslateCompletionResult(completionResult);
|
||||
if (completion.result == VideoIOCompletionResult::DisplayedLate || completion.result == VideoIOCompletionResult::Dropped)
|
||||
{
|
||||
if (mScheduleRealignmentArmed)
|
||||
{
|
||||
mScheduleRealignmentPending = true;
|
||||
mScheduleRealignmentArmed = false;
|
||||
}
|
||||
}
|
||||
else if (completion.result == VideoIOCompletionResult::Completed)
|
||||
{
|
||||
mScheduleRealignmentArmed = true;
|
||||
}
|
||||
completion.outputFrameBuffer = completedSystemBuffer;
|
||||
mOutputFrameCallback(completion);
|
||||
}
|
||||
|
||||
VideoIOCompletionResult DeckLinkSession::TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult)
|
||||
{
|
||||
switch (completionResult)
|
||||
{
|
||||
case bmdOutputFrameDisplayedLate:
|
||||
return VideoIOCompletionResult::DisplayedLate;
|
||||
case bmdOutputFrameDropped:
|
||||
return VideoIOCompletionResult::Dropped;
|
||||
case bmdOutputFrameFlushed:
|
||||
return VideoIOCompletionResult::Flushed;
|
||||
case bmdOutputFrameCompleted:
|
||||
return VideoIOCompletionResult::Completed;
|
||||
default:
|
||||
return VideoIOCompletionResult::Unknown;
|
||||
}
|
||||
}
|
||||
102
src/video/DeckLinkSession.h
Normal file
102
src/video/DeckLinkSession.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
#include "DeckLinkFrameTransfer.h"
|
||||
#include "DeckLinkVideoIOFormat.h"
|
||||
#include "VideoIOFormat.h"
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
#include "VideoPlayoutScheduler.h"
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class OpenGLComposite;
|
||||
|
||||
class DeckLinkSession : public VideoIODevice
|
||||
{
|
||||
public:
|
||||
DeckLinkSession() = default;
|
||||
~DeckLinkSession();
|
||||
|
||||
void ReleaseResources() override;
|
||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) override;
|
||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) override;
|
||||
bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) override;
|
||||
bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) override;
|
||||
bool PrepareOutputSchedule() override;
|
||||
bool StartInputStreams() override;
|
||||
bool StartScheduledPlayback() override;
|
||||
bool Start() override;
|
||||
bool Stop() override;
|
||||
|
||||
bool HasInputDevice() const { return mState.hasInputDevice; }
|
||||
bool HasInputSource() const { return mState.hasInputSource; }
|
||||
void SetInputSourceMissing(bool missing) { mState.hasInputSource = !missing; }
|
||||
bool InputOutputDimensionsDiffer() const { return mState.inputFrameSize != mState.outputFrameSize; }
|
||||
const FrameSize& InputFrameSize() const { return mState.inputFrameSize; }
|
||||
const FrameSize& OutputFrameSize() const { return mState.outputFrameSize; }
|
||||
unsigned InputFrameWidth() const { return mState.inputFrameSize.width; }
|
||||
unsigned InputFrameHeight() const { return mState.inputFrameSize.height; }
|
||||
unsigned OutputFrameWidth() const { return mState.outputFrameSize.width; }
|
||||
unsigned OutputFrameHeight() const { return mState.outputFrameSize.height; }
|
||||
VideoIOPixelFormat InputPixelFormat() const { return mState.inputPixelFormat; }
|
||||
VideoIOPixelFormat OutputPixelFormat() const { return mState.outputPixelFormat; }
|
||||
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.inputPixelFormat); }
|
||||
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(mState.outputPixelFormat); }
|
||||
unsigned InputFrameRowBytes() const { return mState.inputFrameRowBytes; }
|
||||
unsigned OutputFrameRowBytes() const { return mState.outputFrameRowBytes; }
|
||||
unsigned CaptureTextureWidth() const { return mState.captureTextureWidth; }
|
||||
unsigned OutputPackTextureWidth() const { return mState.outputPackTextureWidth; }
|
||||
const std::string& FormatStatusMessage() const { return mState.formatStatusMessage; }
|
||||
const std::string& InputDisplayModeName() const { return mState.inputDisplayModeName; }
|
||||
const std::string& OutputModelName() const { return mState.outputModelName; }
|
||||
bool SupportsInternalKeying() const { return mState.supportsInternalKeying; }
|
||||
bool SupportsExternalKeying() const { return mState.supportsExternalKeying; }
|
||||
bool KeyerInterfaceAvailable() const { return mState.keyerInterfaceAvailable; }
|
||||
bool ExternalKeyingActive() const { return mState.externalKeyingActive; }
|
||||
const std::string& StatusMessage() const { return mState.statusMessage; }
|
||||
void SetStatusMessage(const std::string& message) { mState.statusMessage = message; }
|
||||
const VideoIOState& State() const override { return mState; }
|
||||
VideoIOState& MutableState() override { return mState; }
|
||||
double FrameBudgetMilliseconds() const;
|
||||
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult completionResult, uint64_t readyQueueDepth) override;
|
||||
bool BeginOutputFrame(VideoIOOutputFrame& frame) override;
|
||||
void EndOutputFrame(VideoIOOutputFrame& frame) override;
|
||||
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) override;
|
||||
void HandleVideoInputFrame(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource);
|
||||
void HandlePlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completionResult);
|
||||
|
||||
private:
|
||||
bool AcquireNextOutputVideoFrame(CComPtr<IDeckLinkMutableVideoFrame>& outputVideoFrame);
|
||||
bool PopulateOutputFrame(IDeckLinkMutableVideoFrame* outputVideoFrame, VideoIOOutputFrame& frame);
|
||||
bool ScheduleFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
||||
void UpdateScheduleLeadTelemetry();
|
||||
void MaybeRealignScheduleCursorForLowLead();
|
||||
void RealignScheduleCursorToPlayback();
|
||||
bool ScheduleSystemMemoryFrame(const VideoIOOutputFrame& frame);
|
||||
bool ScheduleBlackFrame(IDeckLinkMutableVideoFrame* outputVideoFrame);
|
||||
void RefreshBufferedVideoFrameCount();
|
||||
static VideoIOCompletionResult TranslateCompletionResult(BMDOutputFrameCompletionResult completionResult);
|
||||
|
||||
CComPtr<CaptureDelegate> captureDelegate;
|
||||
CComPtr<PlayoutDelegate> playoutDelegate;
|
||||
CComPtr<IDeckLinkInput> input;
|
||||
CComPtr<IDeckLinkOutput> output;
|
||||
CComPtr<IDeckLinkKeyer> keyer;
|
||||
std::deque<CComPtr<IDeckLinkMutableVideoFrame>> outputVideoFrameQueue;
|
||||
std::mutex mScheduledSystemFrameMutex;
|
||||
std::unordered_map<IDeckLinkVideoFrame*, void*> mScheduledSystemFrameBuffers;
|
||||
VideoIOState mState;
|
||||
VideoPlayoutPolicy mPlayoutPolicy;
|
||||
VideoPlayoutScheduler mScheduler;
|
||||
bool mScheduleRealignmentPending = false;
|
||||
bool mScheduleRealignmentArmed = true;
|
||||
bool mProactiveScheduleRealignmentArmed = true;
|
||||
InputFrameCallback mInputFrameCallback;
|
||||
OutputFrameCallback mOutputFrameCallback;
|
||||
};
|
||||
28
src/video/DeckLinkVideoIOFormat.cpp
Normal file
28
src/video/DeckLinkVideoIOFormat.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "DeckLinkVideoIOFormat.h"
|
||||
|
||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VideoIOPixelFormat::V210:
|
||||
return bmdFormat10BitYUV;
|
||||
case VideoIOPixelFormat::Yuva10:
|
||||
return bmdFormat10BitYUVA;
|
||||
case VideoIOPixelFormat::Bgra8:
|
||||
return bmdFormat8BitBGRA;
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
default:
|
||||
return bmdFormat8BitYUV;
|
||||
}
|
||||
}
|
||||
|
||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format)
|
||||
{
|
||||
if (format == bmdFormat10BitYUV)
|
||||
return VideoIOPixelFormat::V210;
|
||||
if (format == bmdFormat10BitYUVA)
|
||||
return VideoIOPixelFormat::Yuva10;
|
||||
if (format == bmdFormat8BitBGRA)
|
||||
return VideoIOPixelFormat::Bgra8;
|
||||
return VideoIOPixelFormat::Uyvy8;
|
||||
}
|
||||
7
src/video/DeckLinkVideoIOFormat.h
Normal file
7
src/video/DeckLinkVideoIOFormat.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkAPI_h.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
BMDPixelFormat DeckLinkPixelFormatForVideoIO(VideoIOPixelFormat format);
|
||||
VideoIOPixelFormat VideoIOPixelFormatFromDeckLink(BMDPixelFormat format);
|
||||
89
src/video/OutputProductionController.cpp
Normal file
89
src/video/OutputProductionController.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "OutputProductionController.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::size_t ClampReadyLimit(unsigned value, std::size_t capacity)
|
||||
{
|
||||
const std::size_t requested = static_cast<std::size_t>(value);
|
||||
if (capacity == 0)
|
||||
return requested;
|
||||
return (std::min)(requested, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
OutputProductionController::OutputProductionController(const VideoPlayoutPolicy& policy) :
|
||||
mPolicy(NormalizeVideoPlayoutPolicy(policy))
|
||||
{
|
||||
}
|
||||
|
||||
void OutputProductionController::Configure(const VideoPlayoutPolicy& policy)
|
||||
{
|
||||
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||
}
|
||||
|
||||
OutputProductionDecision OutputProductionController::Decide(const OutputProductionPressure& pressure) const
|
||||
{
|
||||
OutputProductionDecision decision;
|
||||
|
||||
const std::size_t configuredMaxReadyFrames = static_cast<std::size_t>(mPolicy.maxReadyFrames);
|
||||
const std::size_t effectiveMaxReadyFrames = pressure.readyQueueCapacity > 0
|
||||
? (std::min)(configuredMaxReadyFrames, pressure.readyQueueCapacity)
|
||||
: configuredMaxReadyFrames;
|
||||
const std::size_t effectiveTargetReadyFrames = (std::min)(
|
||||
ClampReadyLimit(mPolicy.targetReadyFrames, pressure.readyQueueCapacity),
|
||||
effectiveMaxReadyFrames);
|
||||
|
||||
decision.targetReadyFrames = effectiveTargetReadyFrames;
|
||||
decision.maxReadyFrames = effectiveMaxReadyFrames;
|
||||
|
||||
if (effectiveMaxReadyFrames == 0)
|
||||
{
|
||||
decision.action = OutputProductionAction::Throttle;
|
||||
decision.reason = "no-ready-frame-capacity";
|
||||
return decision;
|
||||
}
|
||||
|
||||
if (pressure.readyQueueDepth >= effectiveMaxReadyFrames)
|
||||
{
|
||||
decision.action = OutputProductionAction::Throttle;
|
||||
decision.reason = "ready-queue-full";
|
||||
return decision;
|
||||
}
|
||||
|
||||
if (pressure.readyQueueDepth < effectiveTargetReadyFrames)
|
||||
{
|
||||
decision.action = OutputProductionAction::Produce;
|
||||
decision.requestedFrames = effectiveTargetReadyFrames - pressure.readyQueueDepth;
|
||||
decision.reason = "ready-queue-below-target";
|
||||
return decision;
|
||||
}
|
||||
|
||||
if ((pressure.lateStreak > 0 || pressure.dropStreak > 0 || pressure.readyQueueUnderrunCount > 0) &&
|
||||
pressure.readyQueueDepth < effectiveMaxReadyFrames)
|
||||
{
|
||||
decision.action = OutputProductionAction::Produce;
|
||||
decision.requestedFrames = 1;
|
||||
decision.reason = "playout-pressure";
|
||||
return decision;
|
||||
}
|
||||
|
||||
decision.action = OutputProductionAction::Wait;
|
||||
decision.reason = "ready-queue-at-target";
|
||||
return decision;
|
||||
}
|
||||
|
||||
const char* OutputProductionActionName(OutputProductionAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case OutputProductionAction::Produce:
|
||||
return "Produce";
|
||||
case OutputProductionAction::Throttle:
|
||||
return "Throttle";
|
||||
case OutputProductionAction::Wait:
|
||||
default:
|
||||
return "Wait";
|
||||
}
|
||||
}
|
||||
46
src/video/OutputProductionController.h
Normal file
46
src/video/OutputProductionController.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
enum class OutputProductionAction
|
||||
{
|
||||
Produce,
|
||||
Wait,
|
||||
Throttle
|
||||
};
|
||||
|
||||
struct OutputProductionPressure
|
||||
{
|
||||
std::size_t readyQueueDepth = 0;
|
||||
std::size_t readyQueueCapacity = 0;
|
||||
uint64_t readyQueueUnderrunCount = 0;
|
||||
uint64_t lateStreak = 0;
|
||||
uint64_t dropStreak = 0;
|
||||
};
|
||||
|
||||
struct OutputProductionDecision
|
||||
{
|
||||
OutputProductionAction action = OutputProductionAction::Wait;
|
||||
std::size_t requestedFrames = 0;
|
||||
std::size_t targetReadyFrames = 0;
|
||||
std::size_t maxReadyFrames = 0;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
class OutputProductionController
|
||||
{
|
||||
public:
|
||||
explicit OutputProductionController(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
|
||||
|
||||
void Configure(const VideoPlayoutPolicy& policy);
|
||||
OutputProductionDecision Decide(const OutputProductionPressure& pressure) const;
|
||||
|
||||
private:
|
||||
VideoPlayoutPolicy mPolicy;
|
||||
};
|
||||
|
||||
const char* OutputProductionActionName(OutputProductionAction action);
|
||||
102
src/video/RenderCadenceController.cpp
Normal file
102
src/video/RenderCadenceController.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "RenderCadenceController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void RenderCadenceController::Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy)
|
||||
{
|
||||
mTargetFrameDuration = IsPositive(targetFrameDuration) ? targetFrameDuration : std::chrono::milliseconds(1);
|
||||
mPolicy = policy;
|
||||
if (mPolicy.skipThresholdFrames < 1.0)
|
||||
mPolicy.skipThresholdFrames = 1.0;
|
||||
Reset(firstRenderTime);
|
||||
}
|
||||
|
||||
void RenderCadenceController::Reset(TimePoint firstRenderTime)
|
||||
{
|
||||
mNextRenderTime = firstRenderTime;
|
||||
mNextFrameIndex = 0;
|
||||
mMetrics = RenderCadenceMetrics();
|
||||
}
|
||||
|
||||
RenderCadenceDecision RenderCadenceController::Tick(TimePoint now)
|
||||
{
|
||||
RenderCadenceDecision decision;
|
||||
decision.frameIndex = mNextFrameIndex;
|
||||
decision.renderTargetTime = mNextRenderTime;
|
||||
decision.nextRenderTime = mNextRenderTime;
|
||||
|
||||
if (now < mNextRenderTime)
|
||||
{
|
||||
decision.action = RenderCadenceAction::Wait;
|
||||
decision.waitDuration = mNextRenderTime - now;
|
||||
decision.reason = "waiting-for-next-render-tick";
|
||||
return decision;
|
||||
}
|
||||
|
||||
const Duration lateness = now - mNextRenderTime;
|
||||
const uint64_t skippedTicks = SkippedTicksForLateness(lateness);
|
||||
if (skippedTicks > 0)
|
||||
{
|
||||
decision.skippedTicks = skippedTicks;
|
||||
decision.frameIndex = mNextFrameIndex + skippedTicks;
|
||||
decision.renderTargetTime = mNextRenderTime + (mTargetFrameDuration * skippedTicks);
|
||||
decision.reason = "late-skip-render-ticks";
|
||||
mMetrics.skippedTickCount += skippedTicks;
|
||||
}
|
||||
else
|
||||
{
|
||||
decision.reason = IsPositive(lateness) ? "late-render-now" : "on-time-render";
|
||||
}
|
||||
|
||||
decision.action = RenderCadenceAction::Render;
|
||||
decision.lateness = now > decision.renderTargetTime
|
||||
? now - decision.renderTargetTime
|
||||
: Duration::zero();
|
||||
mNextFrameIndex = decision.frameIndex + 1;
|
||||
mNextRenderTime = decision.renderTargetTime + mTargetFrameDuration;
|
||||
decision.nextRenderTime = mNextRenderTime;
|
||||
|
||||
++mMetrics.renderedFrameCount;
|
||||
mMetrics.nextFrameIndex = mNextFrameIndex;
|
||||
mMetrics.lastLateness = decision.lateness;
|
||||
if (IsPositive(decision.lateness))
|
||||
{
|
||||
++mMetrics.lateFrameCount;
|
||||
mMetrics.maxLateness = (std::max)(mMetrics.maxLateness, decision.lateness);
|
||||
}
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
uint64_t RenderCadenceController::SkippedTicksForLateness(Duration lateness) const
|
||||
{
|
||||
if (!mPolicy.skipLateTicks || !IsPositive(lateness) || !IsPositive(mTargetFrameDuration))
|
||||
return 0;
|
||||
|
||||
const double lateFrames = static_cast<double>(lateness.count()) / static_cast<double>(mTargetFrameDuration.count());
|
||||
if (lateFrames < mPolicy.skipThresholdFrames)
|
||||
return 0;
|
||||
|
||||
const uint64_t elapsedTicks = static_cast<uint64_t>(std::floor(lateFrames));
|
||||
if (elapsedTicks == 0)
|
||||
return 0;
|
||||
return (std::min)(elapsedTicks, mPolicy.maxSkippedTicksPerDecision);
|
||||
}
|
||||
|
||||
bool RenderCadenceController::IsPositive(Duration duration)
|
||||
{
|
||||
return duration > Duration::zero();
|
||||
}
|
||||
|
||||
const char* RenderCadenceActionName(RenderCadenceAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case RenderCadenceAction::Render:
|
||||
return "Render";
|
||||
case RenderCadenceAction::Wait:
|
||||
default:
|
||||
return "Wait";
|
||||
}
|
||||
}
|
||||
68
src/video/RenderCadenceController.h
Normal file
68
src/video/RenderCadenceController.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
enum class RenderCadenceAction
|
||||
{
|
||||
Wait,
|
||||
Render
|
||||
};
|
||||
|
||||
struct RenderCadencePolicy
|
||||
{
|
||||
bool skipLateTicks = true;
|
||||
uint64_t maxSkippedTicksPerDecision = 4;
|
||||
double skipThresholdFrames = 2.0;
|
||||
};
|
||||
|
||||
struct RenderCadenceDecision
|
||||
{
|
||||
RenderCadenceAction action = RenderCadenceAction::Wait;
|
||||
uint64_t frameIndex = 0;
|
||||
uint64_t skippedTicks = 0;
|
||||
std::chrono::steady_clock::time_point renderTargetTime;
|
||||
std::chrono::steady_clock::time_point nextRenderTime;
|
||||
std::chrono::steady_clock::duration waitDuration = std::chrono::steady_clock::duration::zero();
|
||||
std::chrono::steady_clock::duration lateness = std::chrono::steady_clock::duration::zero();
|
||||
const char* reason = "waiting-for-next-render-tick";
|
||||
};
|
||||
|
||||
struct RenderCadenceMetrics
|
||||
{
|
||||
uint64_t nextFrameIndex = 0;
|
||||
uint64_t renderedFrameCount = 0;
|
||||
uint64_t skippedTickCount = 0;
|
||||
uint64_t lateFrameCount = 0;
|
||||
std::chrono::steady_clock::duration lastLateness = std::chrono::steady_clock::duration::zero();
|
||||
std::chrono::steady_clock::duration maxLateness = std::chrono::steady_clock::duration::zero();
|
||||
};
|
||||
|
||||
class RenderCadenceController
|
||||
{
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
using Duration = Clock::duration;
|
||||
|
||||
void Configure(Duration targetFrameDuration, TimePoint firstRenderTime, const RenderCadencePolicy& policy = RenderCadencePolicy());
|
||||
void Reset(TimePoint firstRenderTime);
|
||||
RenderCadenceDecision Tick(TimePoint now);
|
||||
|
||||
Duration TargetFrameDuration() const { return mTargetFrameDuration; }
|
||||
TimePoint NextRenderTime() const { return mNextRenderTime; }
|
||||
uint64_t NextFrameIndex() const { return mNextFrameIndex; }
|
||||
const RenderCadenceMetrics& Metrics() const { return mMetrics; }
|
||||
|
||||
private:
|
||||
uint64_t SkippedTicksForLateness(Duration lateness) const;
|
||||
static bool IsPositive(Duration duration);
|
||||
|
||||
Duration mTargetFrameDuration = std::chrono::milliseconds(16);
|
||||
TimePoint mNextRenderTime;
|
||||
uint64_t mNextFrameIndex = 0;
|
||||
RenderCadencePolicy mPolicy;
|
||||
RenderCadenceMetrics mMetrics;
|
||||
};
|
||||
|
||||
const char* RenderCadenceActionName(RenderCadenceAction action);
|
||||
93
src/video/RenderOutputQueue.cpp
Normal file
93
src/video/RenderOutputQueue.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "RenderOutputQueue.h"
|
||||
|
||||
RenderOutputQueue::RenderOutputQueue(const VideoPlayoutPolicy& policy) :
|
||||
mPolicy(NormalizeVideoPlayoutPolicy(policy))
|
||||
{
|
||||
}
|
||||
|
||||
void RenderOutputQueue::Configure(const VideoPlayoutPolicy& policy)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||
while (mReadyFrames.size() > CapacityLocked())
|
||||
{
|
||||
ReleaseFrame(mReadyFrames.front());
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::Push(RenderOutputFrame frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.size() >= CapacityLocked())
|
||||
{
|
||||
ReleaseFrame(mReadyFrames.front());
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
}
|
||||
|
||||
mReadyFrames.push_back(frame);
|
||||
++mPushedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::TryPop(RenderOutputFrame& frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.empty())
|
||||
{
|
||||
++mUnderrunCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame = mReadyFrames.front();
|
||||
mReadyFrames.pop_front();
|
||||
++mPoppedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderOutputQueue::DropOldestFrame()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mReadyFrames.empty())
|
||||
return false;
|
||||
|
||||
ReleaseFrame(mReadyFrames.front());
|
||||
mReadyFrames.pop_front();
|
||||
++mDroppedCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderOutputQueue::Clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (RenderOutputFrame& frame : mReadyFrames)
|
||||
ReleaseFrame(frame);
|
||||
mReadyFrames.clear();
|
||||
}
|
||||
|
||||
RenderOutputQueueMetrics RenderOutputQueue::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
RenderOutputQueueMetrics metrics;
|
||||
metrics.depth = mReadyFrames.size();
|
||||
metrics.capacity = CapacityLocked();
|
||||
metrics.pushedCount = mPushedCount;
|
||||
metrics.poppedCount = mPoppedCount;
|
||||
metrics.droppedCount = mDroppedCount;
|
||||
metrics.underrunCount = mUnderrunCount;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
std::size_t RenderOutputQueue::CapacityLocked() const
|
||||
{
|
||||
return static_cast<std::size_t>(mPolicy.maxReadyFrames);
|
||||
}
|
||||
|
||||
void RenderOutputQueue::ReleaseFrame(RenderOutputFrame& frame)
|
||||
{
|
||||
if (frame.releaseFrame)
|
||||
frame.releaseFrame(frame.frame);
|
||||
frame.releaseFrame = {};
|
||||
}
|
||||
52
src/video/RenderOutputQueue.h
Normal file
52
src/video/RenderOutputQueue.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
struct RenderOutputFrame
|
||||
{
|
||||
VideoIOOutputFrame frame;
|
||||
uint64_t frameIndex = 0;
|
||||
bool stale = false;
|
||||
std::function<void(VideoIOOutputFrame& frame)> releaseFrame;
|
||||
};
|
||||
|
||||
struct RenderOutputQueueMetrics
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
std::size_t capacity = 0;
|
||||
uint64_t pushedCount = 0;
|
||||
uint64_t poppedCount = 0;
|
||||
uint64_t droppedCount = 0;
|
||||
uint64_t underrunCount = 0;
|
||||
};
|
||||
|
||||
class RenderOutputQueue
|
||||
{
|
||||
public:
|
||||
explicit RenderOutputQueue(const VideoPlayoutPolicy& policy = VideoPlayoutPolicy());
|
||||
|
||||
void Configure(const VideoPlayoutPolicy& policy);
|
||||
bool Push(RenderOutputFrame frame);
|
||||
bool TryPop(RenderOutputFrame& frame);
|
||||
bool DropOldestFrame();
|
||||
void Clear();
|
||||
RenderOutputQueueMetrics GetMetrics() const;
|
||||
|
||||
private:
|
||||
std::size_t CapacityLocked() const;
|
||||
static void ReleaseFrame(RenderOutputFrame& frame);
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
VideoPlayoutPolicy mPolicy;
|
||||
std::deque<RenderOutputFrame> mReadyFrames;
|
||||
uint64_t mPushedCount = 0;
|
||||
uint64_t mPoppedCount = 0;
|
||||
uint64_t mDroppedCount = 0;
|
||||
uint64_t mUnderrunCount = 0;
|
||||
};
|
||||
260
src/video/SystemOutputFramePool.cpp
Normal file
260
src/video/SystemOutputFramePool.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include "SystemOutputFramePool.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
SystemOutputFramePoolConfig NormalizeConfig(SystemOutputFramePoolConfig config)
|
||||
{
|
||||
if (config.rowBytes == 0)
|
||||
config.rowBytes = VideoIORowBytes(config.pixelFormat, config.width);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
SystemOutputFramePool::SystemOutputFramePool(const SystemOutputFramePoolConfig& config)
|
||||
{
|
||||
Configure(config);
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::Configure(const SystemOutputFramePoolConfig& config)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mConfig = NormalizeConfig(config);
|
||||
mReadySlots.clear();
|
||||
mSlots.clear();
|
||||
mSlots.resize(mConfig.capacity);
|
||||
|
||||
const std::size_t byteCount = FrameByteCount();
|
||||
for (StoredSlot& slot : mSlots)
|
||||
{
|
||||
slot.bytes.resize(byteCount);
|
||||
slot.state = OutputFrameSlotState::Free;
|
||||
++slot.generation;
|
||||
}
|
||||
|
||||
mAcquireMissCount = 0;
|
||||
mReadyUnderrunCount = 0;
|
||||
}
|
||||
|
||||
SystemOutputFramePoolConfig SystemOutputFramePool::Config() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::AcquireFreeSlot(OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
if (mSlots[index].state != OutputFrameSlotState::Free)
|
||||
continue;
|
||||
|
||||
mSlots[index].state = OutputFrameSlotState::Rendering;
|
||||
++mSlots[index].generation;
|
||||
FillOutputSlotLocked(index, slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
slot = OutputFrameSlot();
|
||||
++mAcquireMissCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::AcquireRenderingSlot(OutputFrameSlot& slot)
|
||||
{
|
||||
return AcquireFreeSlot(slot);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::PublishReadySlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!TransitionSlotLocked(slot, OutputFrameSlotState::Rendering, OutputFrameSlotState::Completed))
|
||||
return false;
|
||||
|
||||
mReadySlots.push_back(slot.index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::PublishCompletedSlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
return PublishReadySlot(slot);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ConsumeReadySlot(OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
while (!mReadySlots.empty())
|
||||
{
|
||||
const std::size_t index = mReadySlots.front();
|
||||
mReadySlots.pop_front();
|
||||
if (index >= mSlots.size() || mSlots[index].state != OutputFrameSlotState::Completed)
|
||||
continue;
|
||||
|
||||
FillOutputSlotLocked(index, slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
slot = OutputFrameSlot();
|
||||
++mReadyUnderrunCount;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ConsumeCompletedSlot(OutputFrameSlot& slot)
|
||||
{
|
||||
return ConsumeReadySlot(slot);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::MarkScheduled(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!IsValidSlotLocked(slot))
|
||||
return false;
|
||||
if (mSlots[slot.index].state != OutputFrameSlotState::Completed)
|
||||
return false;
|
||||
|
||||
RemoveReadyIndexLocked(slot.index);
|
||||
mSlots[slot.index].state = OutputFrameSlotState::Scheduled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::MarkScheduledByBuffer(void* bytes)
|
||||
{
|
||||
if (bytes == nullptr)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
if (mSlots[index].bytes.empty() || mSlots[index].bytes.data() != bytes)
|
||||
continue;
|
||||
if (mSlots[index].state != OutputFrameSlotState::Completed)
|
||||
return false;
|
||||
|
||||
RemoveReadyIndexLocked(index);
|
||||
mSlots[index].state = OutputFrameSlotState::Scheduled;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseSlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!IsValidSlotLocked(slot) || mSlots[slot.index].state == OutputFrameSlotState::Free)
|
||||
return false;
|
||||
|
||||
return ReleaseSlotByIndexLocked(slot.index);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseScheduledSlot(const OutputFrameSlot& slot)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return TransitionSlotLocked(slot, OutputFrameSlotState::Scheduled, OutputFrameSlotState::Free);
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseSlotByBuffer(void* bytes)
|
||||
{
|
||||
if (bytes == nullptr)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
if (!mSlots[index].bytes.empty() && mSlots[index].bytes.data() == bytes)
|
||||
return ReleaseSlotByIndexLocked(index);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::Clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mReadySlots.clear();
|
||||
for (StoredSlot& slot : mSlots)
|
||||
{
|
||||
slot.state = OutputFrameSlotState::Free;
|
||||
++slot.generation;
|
||||
}
|
||||
}
|
||||
|
||||
SystemOutputFramePoolMetrics SystemOutputFramePool::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
SystemOutputFramePoolMetrics metrics;
|
||||
metrics.capacity = mSlots.size();
|
||||
metrics.readyCount = mReadySlots.size();
|
||||
metrics.acquireMissCount = mAcquireMissCount;
|
||||
metrics.readyUnderrunCount = mReadyUnderrunCount;
|
||||
|
||||
for (const StoredSlot& slot : mSlots)
|
||||
{
|
||||
switch (slot.state)
|
||||
{
|
||||
case OutputFrameSlotState::Free:
|
||||
++metrics.freeCount;
|
||||
break;
|
||||
case OutputFrameSlotState::Rendering:
|
||||
++metrics.renderingCount;
|
||||
++metrics.acquiredCount;
|
||||
break;
|
||||
case OutputFrameSlotState::Completed:
|
||||
++metrics.completedCount;
|
||||
break;
|
||||
case OutputFrameSlotState::Scheduled:
|
||||
++metrics.scheduledCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::IsValidSlotLocked(const OutputFrameSlot& slot) const
|
||||
{
|
||||
return slot.index < mSlots.size() && mSlots[slot.index].generation == slot.generation;
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState)
|
||||
{
|
||||
if (!IsValidSlotLocked(slot) || mSlots[slot.index].state != expectedState)
|
||||
return false;
|
||||
|
||||
mSlots[slot.index].state = nextState;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot)
|
||||
{
|
||||
StoredSlot& storedSlot = mSlots[index];
|
||||
slot.index = index;
|
||||
slot.generation = storedSlot.generation;
|
||||
slot.frame.bytes = storedSlot.bytes.empty() ? nullptr : storedSlot.bytes.data();
|
||||
slot.frame.rowBytes = static_cast<long>(mConfig.rowBytes);
|
||||
slot.frame.width = mConfig.width;
|
||||
slot.frame.height = mConfig.height;
|
||||
slot.frame.pixelFormat = mConfig.pixelFormat;
|
||||
slot.frame.nativeFrame = nullptr;
|
||||
slot.frame.nativeBuffer = slot.frame.bytes;
|
||||
}
|
||||
|
||||
void SystemOutputFramePool::RemoveReadyIndexLocked(std::size_t index)
|
||||
{
|
||||
mReadySlots.erase(std::remove(mReadySlots.begin(), mReadySlots.end(), index), mReadySlots.end());
|
||||
}
|
||||
|
||||
bool SystemOutputFramePool::ReleaseSlotByIndexLocked(std::size_t index)
|
||||
{
|
||||
if (index >= mSlots.size() || mSlots[index].state == OutputFrameSlotState::Free)
|
||||
return false;
|
||||
|
||||
RemoveReadyIndexLocked(index);
|
||||
mSlots[index].state = OutputFrameSlotState::Free;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t SystemOutputFramePool::FrameByteCount() const
|
||||
{
|
||||
return static_cast<std::size_t>(mConfig.rowBytes) * static_cast<std::size_t>(mConfig.height);
|
||||
}
|
||||
94
src/video/SystemOutputFramePool.h
Normal file
94
src/video/SystemOutputFramePool.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
enum class OutputFrameSlotState
|
||||
{
|
||||
Free,
|
||||
Rendering,
|
||||
Completed,
|
||||
Scheduled
|
||||
};
|
||||
|
||||
struct SystemOutputFramePoolConfig
|
||||
{
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
unsigned rowBytes = 0;
|
||||
std::size_t capacity = 0;
|
||||
};
|
||||
|
||||
struct OutputFrameSlot
|
||||
{
|
||||
VideoIOOutputFrame frame;
|
||||
std::size_t index = 0;
|
||||
uint64_t generation = 0;
|
||||
};
|
||||
|
||||
struct SystemOutputFramePoolMetrics
|
||||
{
|
||||
std::size_t capacity = 0;
|
||||
std::size_t freeCount = 0;
|
||||
std::size_t renderingCount = 0;
|
||||
std::size_t completedCount = 0;
|
||||
std::size_t scheduledCount = 0;
|
||||
std::size_t acquiredCount = 0;
|
||||
std::size_t readyCount = 0;
|
||||
std::size_t consumedCount = 0;
|
||||
uint64_t acquireMissCount = 0;
|
||||
uint64_t readyUnderrunCount = 0;
|
||||
};
|
||||
|
||||
class SystemOutputFramePool
|
||||
{
|
||||
public:
|
||||
SystemOutputFramePool() = default;
|
||||
explicit SystemOutputFramePool(const SystemOutputFramePoolConfig& config);
|
||||
|
||||
void Configure(const SystemOutputFramePoolConfig& config);
|
||||
SystemOutputFramePoolConfig Config() const;
|
||||
|
||||
bool AcquireFreeSlot(OutputFrameSlot& slot);
|
||||
bool AcquireRenderingSlot(OutputFrameSlot& slot);
|
||||
bool PublishReadySlot(const OutputFrameSlot& slot);
|
||||
bool PublishCompletedSlot(const OutputFrameSlot& slot);
|
||||
bool ConsumeReadySlot(OutputFrameSlot& slot);
|
||||
bool ConsumeCompletedSlot(OutputFrameSlot& slot);
|
||||
bool MarkScheduled(const OutputFrameSlot& slot);
|
||||
bool MarkScheduledByBuffer(void* bytes);
|
||||
bool ReleaseSlot(const OutputFrameSlot& slot);
|
||||
bool ReleaseScheduledSlot(const OutputFrameSlot& slot);
|
||||
bool ReleaseSlotByBuffer(void* bytes);
|
||||
void Clear();
|
||||
|
||||
SystemOutputFramePoolMetrics GetMetrics() const;
|
||||
|
||||
private:
|
||||
struct StoredSlot
|
||||
{
|
||||
std::vector<unsigned char> bytes;
|
||||
OutputFrameSlotState state = OutputFrameSlotState::Free;
|
||||
uint64_t generation = 1;
|
||||
};
|
||||
|
||||
bool IsValidSlotLocked(const OutputFrameSlot& slot) const;
|
||||
bool TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState);
|
||||
void FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot);
|
||||
void RemoveReadyIndexLocked(std::size_t index);
|
||||
bool ReleaseSlotByIndexLocked(std::size_t index);
|
||||
std::size_t FrameByteCount() const;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
SystemOutputFramePoolConfig mConfig;
|
||||
std::vector<StoredSlot> mSlots;
|
||||
std::deque<std::size_t> mReadySlots;
|
||||
uint64_t mAcquireMissCount = 0;
|
||||
uint64_t mReadyUnderrunCount = 0;
|
||||
};
|
||||
1095
src/video/VideoBackend.cpp
Normal file
1095
src/video/VideoBackend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
161
src/video/VideoBackend.h
Normal file
161
src/video/VideoBackend.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include "OutputProductionController.h"
|
||||
#include "RenderCadenceController.h"
|
||||
#include "RenderOutputQueue.h"
|
||||
#include "SystemOutputFramePool.h"
|
||||
#include "VideoBackendLifecycle.h"
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
class HealthTelemetry;
|
||||
class OpenGLVideoIOBridge;
|
||||
class RenderEngine;
|
||||
class RuntimeEventDispatcher;
|
||||
class VideoIODevice;
|
||||
|
||||
class VideoBackend
|
||||
{
|
||||
public:
|
||||
VideoBackend(RenderEngine& renderEngine, HealthTelemetry& healthTelemetry, RuntimeEventDispatcher& runtimeEventDispatcher);
|
||||
~VideoBackend();
|
||||
|
||||
void ReleaseResources();
|
||||
VideoBackendLifecycleState LifecycleState() const;
|
||||
bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error);
|
||||
bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error);
|
||||
bool ConfigureInput(const VideoFormat& inputVideoMode, std::string& error);
|
||||
bool ConfigureOutput(const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error);
|
||||
bool Start();
|
||||
bool Stop();
|
||||
|
||||
const VideoIOState& State() const;
|
||||
VideoIOState& MutableState();
|
||||
bool BeginOutputFrame(VideoIOOutputFrame& frame);
|
||||
void EndOutputFrame(VideoIOOutputFrame& frame);
|
||||
bool ScheduleOutputFrame(const VideoIOOutputFrame& frame);
|
||||
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth);
|
||||
void RecordBackendPlayoutHealth(VideoIOCompletionResult result, const VideoPlayoutRecoveryDecision& recoveryDecision);
|
||||
|
||||
bool HasInputDevice() const;
|
||||
bool HasInputSource() const;
|
||||
unsigned InputFrameWidth() const;
|
||||
unsigned InputFrameHeight() const;
|
||||
unsigned OutputFrameWidth() const;
|
||||
unsigned OutputFrameHeight() const;
|
||||
unsigned CaptureTextureWidth() const;
|
||||
unsigned OutputPackTextureWidth() const;
|
||||
VideoIOPixelFormat InputPixelFormat() const;
|
||||
const std::string& InputDisplayModeName() const;
|
||||
const std::string& OutputModelName() const;
|
||||
bool SupportsInternalKeying() const;
|
||||
bool SupportsExternalKeying() const;
|
||||
bool KeyerInterfaceAvailable() const;
|
||||
bool ExternalKeyingActive() const;
|
||||
const std::string& StatusMessage() const;
|
||||
bool ShouldPrioritizeOutputOverPreview() const;
|
||||
void SetStatusMessage(const std::string& message);
|
||||
void PublishStatus(bool externalKeyingConfigured, const std::string& statusMessage = std::string());
|
||||
void ReportNoInputDeviceSignalStatus();
|
||||
|
||||
private:
|
||||
void HandleInputFrame(const VideoIOFrame& frame);
|
||||
void HandleOutputFrameCompletion(const VideoIOCompletion& completion);
|
||||
void StartOutputCompletionWorker();
|
||||
void StopOutputCompletionWorker();
|
||||
void OutputCompletionWorkerMain();
|
||||
void StartOutputProducerWorker();
|
||||
void StopOutputProducerWorker();
|
||||
void OutputProducerWorkerMain();
|
||||
void NotifyOutputProducer();
|
||||
bool WarmupOutputPreroll();
|
||||
std::chrono::milliseconds OutputProducerWakeInterval() const;
|
||||
void ProcessOutputFrameCompletion(const VideoIOCompletion& completion);
|
||||
std::size_t ProduceReadyOutputFrames(const VideoIOCompletion& completion, std::size_t maxFrames);
|
||||
OutputProductionPressure BuildOutputProductionPressure(const RenderOutputQueueMetrics& metrics) const;
|
||||
bool RenderReadyOutputFrame(const VideoIOState& state, const VideoIOCompletion& completion);
|
||||
std::size_t ScheduleReadyOutputFramesToTarget();
|
||||
bool ScheduleReadyOutputFrame();
|
||||
bool ScheduleBlackUnderrunFrame();
|
||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||
void RecordReadyQueueDepthSample(const RenderOutputQueueMetrics& metrics);
|
||||
void RecordDeckLinkBufferTelemetry();
|
||||
void RecordSystemMemoryPlayoutStats();
|
||||
void RecordOutputRenderDuration(double renderMilliseconds, double acquireMilliseconds, double renderRequestMilliseconds, double endAccessMilliseconds);
|
||||
bool ApplyLifecycleTransition(VideoBackendLifecycleState state, const std::string& message);
|
||||
bool ApplyLifecycleFailure(const std::string& message);
|
||||
void PublishBackendStateChanged(const std::string& state, const std::string& message);
|
||||
void PublishInputSignalChanged(const VideoIOFrame& frame, const VideoIOState& state);
|
||||
void PublishInputFrameArrived(const VideoIOFrame& frame);
|
||||
void PublishOutputFrameScheduled(const VideoIOOutputFrame& frame);
|
||||
void PublishOutputFrameCompleted(const VideoIOCompletion& completion);
|
||||
void PublishTimingSample(const std::string& subsystem, const std::string& metric, double value, const std::string& unit);
|
||||
static std::string CompletionResultName(VideoIOCompletionResult result);
|
||||
static std::string PixelFormatName(VideoIOPixelFormat pixelFormat);
|
||||
static bool IsEnvironmentFlagEnabled(const char* name);
|
||||
|
||||
HealthTelemetry& mHealthTelemetry;
|
||||
RuntimeEventDispatcher& mRuntimeEventDispatcher;
|
||||
VideoBackendLifecycle mLifecycle;
|
||||
VideoPlayoutPolicy mPlayoutPolicy;
|
||||
OutputProductionController mOutputProductionController;
|
||||
RenderCadenceController mRenderCadenceController;
|
||||
RenderOutputQueue mReadyOutputQueue;
|
||||
SystemOutputFramePool mSystemOutputFramePool;
|
||||
std::unique_ptr<VideoIODevice> mVideoIODevice;
|
||||
std::unique_ptr<OpenGLVideoIOBridge> mBridge;
|
||||
std::mutex mOutputCompletionMutex;
|
||||
std::condition_variable mOutputCompletionCondition;
|
||||
std::deque<VideoIOCompletion> mPendingOutputCompletions;
|
||||
std::thread mOutputCompletionWorker;
|
||||
std::mutex mOutputProducerMutex;
|
||||
std::condition_variable mOutputProducerCondition;
|
||||
std::thread mOutputProducerWorker;
|
||||
VideoIOCompletion mLastOutputProductionCompletion;
|
||||
std::chrono::steady_clock::time_point mLastOutputProductionTime;
|
||||
std::mutex mOutputProductionMutex;
|
||||
std::mutex mOutputSchedulingMutex;
|
||||
mutable std::mutex mOutputMetricsMutex;
|
||||
bool mOutputCompletionWorkerRunning = false;
|
||||
bool mOutputCompletionWorkerStopping = false;
|
||||
bool mOutputProducerWorkerRunning = false;
|
||||
bool mOutputProducerWorkerStopping = false;
|
||||
bool mInputCaptureDisabled = false;
|
||||
uint64_t mNextReadyOutputFrameIndex = 0;
|
||||
uint64_t mInputFrameIndex = 0;
|
||||
uint64_t mOutputFrameScheduleIndex = 0;
|
||||
uint64_t mOutputFrameCompletionIndex = 0;
|
||||
bool mHasLastInputSignal = false;
|
||||
bool mLastInputSignal = false;
|
||||
unsigned mLastInputSignalWidth = 0;
|
||||
unsigned mLastInputSignalHeight = 0;
|
||||
std::string mLastInputSignalModeName;
|
||||
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
|
||||
double mCompletionIntervalMilliseconds = 0.0;
|
||||
double mSmoothedCompletionIntervalMilliseconds = 0.0;
|
||||
double mMaxCompletionIntervalMilliseconds = 0.0;
|
||||
bool mHasReadyQueueDepthBaseline = false;
|
||||
std::size_t mMinReadyQueueDepth = 0;
|
||||
std::size_t mMaxReadyQueueDepth = 0;
|
||||
uint64_t mReadyQueueZeroDepthCount = 0;
|
||||
double mOutputRenderMilliseconds = 0.0;
|
||||
double mSmoothedOutputRenderMilliseconds = 0.0;
|
||||
double mMaxOutputRenderMilliseconds = 0.0;
|
||||
double mOutputFrameAcquireMilliseconds = 0.0;
|
||||
double mOutputFrameRenderRequestMilliseconds = 0.0;
|
||||
double mOutputFrameEndAccessMilliseconds = 0.0;
|
||||
uint64_t mLastLateStreak = 0;
|
||||
uint64_t mLastDropStreak = 0;
|
||||
uint64_t mLateFrameCount = 0;
|
||||
uint64_t mDroppedFrameCount = 0;
|
||||
uint64_t mFlushedFrameCount = 0;
|
||||
};
|
||||
123
src/video/VideoBackendLifecycle.cpp
Normal file
123
src/video/VideoBackendLifecycle.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "VideoBackendLifecycle.h"
|
||||
|
||||
VideoBackendLifecycleState VideoBackendLifecycle::State() const
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
const std::string& VideoBackendLifecycle::FailureReason() const
|
||||
{
|
||||
return mFailureReason;
|
||||
}
|
||||
|
||||
VideoBackendLifecycleTransition VideoBackendLifecycle::TransitionTo(VideoBackendLifecycleState next, const std::string& reason)
|
||||
{
|
||||
VideoBackendLifecycleTransition transition;
|
||||
transition.previous = mState;
|
||||
transition.current = next;
|
||||
transition.reason = reason;
|
||||
transition.accepted = CanTransition(mState, next);
|
||||
if (!transition.accepted)
|
||||
{
|
||||
transition.current = mState;
|
||||
transition.errorMessage = std::string("Invalid video backend lifecycle transition from ") +
|
||||
StateName(mState) + " to " + StateName(next) + ".";
|
||||
return transition;
|
||||
}
|
||||
|
||||
mState = next;
|
||||
transition.current = mState;
|
||||
if (mState != VideoBackendLifecycleState::Failed)
|
||||
mFailureReason.clear();
|
||||
return transition;
|
||||
}
|
||||
|
||||
VideoBackendLifecycleTransition VideoBackendLifecycle::Fail(const std::string& reason)
|
||||
{
|
||||
VideoBackendLifecycleTransition transition = TransitionTo(VideoBackendLifecycleState::Failed, reason);
|
||||
if (transition.accepted)
|
||||
mFailureReason = reason;
|
||||
return transition;
|
||||
}
|
||||
|
||||
bool VideoBackendLifecycle::CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next)
|
||||
{
|
||||
if (current == next)
|
||||
return true;
|
||||
|
||||
switch (current)
|
||||
{
|
||||
case VideoBackendLifecycleState::Uninitialized:
|
||||
return next == VideoBackendLifecycleState::Discovering ||
|
||||
next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Discovering:
|
||||
return next == VideoBackendLifecycleState::Discovered ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Discovered:
|
||||
return next == VideoBackendLifecycleState::Configuring ||
|
||||
next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Configuring:
|
||||
return next == VideoBackendLifecycleState::Configured ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Configured:
|
||||
return next == VideoBackendLifecycleState::Prerolling ||
|
||||
next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Prerolling:
|
||||
return next == VideoBackendLifecycleState::Running ||
|
||||
next == VideoBackendLifecycleState::Stopping ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Running:
|
||||
return next == VideoBackendLifecycleState::Degraded ||
|
||||
next == VideoBackendLifecycleState::Stopping ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Degraded:
|
||||
return next == VideoBackendLifecycleState::Running ||
|
||||
next == VideoBackendLifecycleState::Stopping ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Stopping:
|
||||
return next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Stopped:
|
||||
return next == VideoBackendLifecycleState::Discovering ||
|
||||
next == VideoBackendLifecycleState::Failed;
|
||||
case VideoBackendLifecycleState::Failed:
|
||||
return next == VideoBackendLifecycleState::Stopped ||
|
||||
next == VideoBackendLifecycleState::Discovering;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* VideoBackendLifecycle::StateName(VideoBackendLifecycleState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case VideoBackendLifecycleState::Uninitialized:
|
||||
return "uninitialized";
|
||||
case VideoBackendLifecycleState::Discovering:
|
||||
return "discovering";
|
||||
case VideoBackendLifecycleState::Discovered:
|
||||
return "discovered";
|
||||
case VideoBackendLifecycleState::Configuring:
|
||||
return "configuring";
|
||||
case VideoBackendLifecycleState::Configured:
|
||||
return "configured";
|
||||
case VideoBackendLifecycleState::Prerolling:
|
||||
return "prerolling";
|
||||
case VideoBackendLifecycleState::Running:
|
||||
return "running";
|
||||
case VideoBackendLifecycleState::Degraded:
|
||||
return "degraded";
|
||||
case VideoBackendLifecycleState::Stopping:
|
||||
return "stopping";
|
||||
case VideoBackendLifecycleState::Stopped:
|
||||
return "stopped";
|
||||
case VideoBackendLifecycleState::Failed:
|
||||
return "failed";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
43
src/video/VideoBackendLifecycle.h
Normal file
43
src/video/VideoBackendLifecycle.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
enum class VideoBackendLifecycleState
|
||||
{
|
||||
Uninitialized,
|
||||
Discovering,
|
||||
Discovered,
|
||||
Configuring,
|
||||
Configured,
|
||||
Prerolling,
|
||||
Running,
|
||||
Degraded,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Failed
|
||||
};
|
||||
|
||||
struct VideoBackendLifecycleTransition
|
||||
{
|
||||
VideoBackendLifecycleState previous = VideoBackendLifecycleState::Uninitialized;
|
||||
VideoBackendLifecycleState current = VideoBackendLifecycleState::Uninitialized;
|
||||
bool accepted = false;
|
||||
std::string reason;
|
||||
std::string errorMessage;
|
||||
};
|
||||
|
||||
class VideoBackendLifecycle
|
||||
{
|
||||
public:
|
||||
VideoBackendLifecycleState State() const;
|
||||
const std::string& FailureReason() const;
|
||||
VideoBackendLifecycleTransition TransitionTo(VideoBackendLifecycleState next, const std::string& reason);
|
||||
VideoBackendLifecycleTransition Fail(const std::string& reason);
|
||||
|
||||
static bool CanTransition(VideoBackendLifecycleState current, VideoBackendLifecycleState next);
|
||||
static const char* StateName(VideoBackendLifecycleState state);
|
||||
|
||||
private:
|
||||
VideoBackendLifecycleState mState = VideoBackendLifecycleState::Uninitialized;
|
||||
std::string mFailureReason;
|
||||
};
|
||||
170
src/video/VideoIOFormat.cpp
Normal file
170
src/video/VideoIOFormat.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
uint16_t Clamp10(int value, int minimum, int maximum)
|
||||
{
|
||||
return static_cast<uint16_t>(std::max(minimum, std::min(maximum, value)));
|
||||
}
|
||||
|
||||
uint32_t MakeV210Word(uint16_t a, uint16_t b, uint16_t c)
|
||||
{
|
||||
return (static_cast<uint32_t>(a) & 0x3ffu)
|
||||
| ((static_cast<uint32_t>(b) & 0x3ffu) << 10)
|
||||
| ((static_cast<uint32_t>(c) & 0x3ffu) << 20);
|
||||
}
|
||||
|
||||
void StoreWord(std::array<uint8_t, 16>& bytes, std::size_t wordIndex, uint32_t word)
|
||||
{
|
||||
const std::size_t offset = wordIndex * 4;
|
||||
bytes[offset + 0] = static_cast<uint8_t>(word & 0xffu);
|
||||
bytes[offset + 1] = static_cast<uint8_t>((word >> 8) & 0xffu);
|
||||
bytes[offset + 2] = static_cast<uint8_t>((word >> 16) & 0xffu);
|
||||
bytes[offset + 3] = static_cast<uint8_t>((word >> 24) & 0xffu);
|
||||
}
|
||||
|
||||
uint32_t LoadWord(const std::array<uint8_t, 16>& bytes, std::size_t wordIndex)
|
||||
{
|
||||
const std::size_t offset = wordIndex * 4;
|
||||
return static_cast<uint32_t>(bytes[offset + 0])
|
||||
| (static_cast<uint32_t>(bytes[offset + 1]) << 8)
|
||||
| (static_cast<uint32_t>(bytes[offset + 2]) << 16)
|
||||
| (static_cast<uint32_t>(bytes[offset + 3]) << 24);
|
||||
}
|
||||
|
||||
uint16_t Component(uint32_t word, unsigned index)
|
||||
{
|
||||
return static_cast<uint16_t>((word >> (index * 10)) & 0x3ffu);
|
||||
}
|
||||
}
|
||||
|
||||
const char* VideoIOPixelFormatName(VideoIOPixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VideoIOPixelFormat::V210:
|
||||
return "10-bit YUV v210";
|
||||
case VideoIOPixelFormat::Yuva10:
|
||||
return "10-bit YUVA Ay10";
|
||||
case VideoIOPixelFormat::Bgra8:
|
||||
return "8-bit BGRA";
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
default:
|
||||
return "8-bit YUV UYVY";
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format)
|
||||
{
|
||||
return format == VideoIOPixelFormat::V210 || format == VideoIOPixelFormat::Yuva10;
|
||||
}
|
||||
|
||||
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported)
|
||||
{
|
||||
return tenBitSupported ? VideoIOPixelFormat::V210 : VideoIOPixelFormat::Uyvy8;
|
||||
}
|
||||
|
||||
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VideoIOPixelFormat::Uyvy8:
|
||||
return 2u;
|
||||
case VideoIOPixelFormat::Bgra8:
|
||||
return 4u;
|
||||
case VideoIOPixelFormat::Yuva10:
|
||||
return 4u;
|
||||
case VideoIOPixelFormat::V210:
|
||||
default:
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth)
|
||||
{
|
||||
if (format == VideoIOPixelFormat::V210)
|
||||
return MinimumV210RowBytes(frameWidth);
|
||||
if (format == VideoIOPixelFormat::Yuva10)
|
||||
return MinimumYuva10RowBytes(frameWidth);
|
||||
return frameWidth * VideoIOBytesPerPixel(format);
|
||||
}
|
||||
|
||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes)
|
||||
{
|
||||
return (rowBytes + 3u) / 4u;
|
||||
}
|
||||
|
||||
unsigned MinimumV210RowBytes(unsigned frameWidth)
|
||||
{
|
||||
return ((frameWidth + 5u) / 6u) * 16u;
|
||||
}
|
||||
|
||||
unsigned MinimumYuva10RowBytes(unsigned frameWidth)
|
||||
{
|
||||
return ((frameWidth + 63u) / 64u) * 256u;
|
||||
}
|
||||
|
||||
unsigned ActiveV210WordsForWidth(unsigned frameWidth)
|
||||
{
|
||||
return ((frameWidth + 5u) / 6u) * 4u;
|
||||
}
|
||||
|
||||
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue)
|
||||
{
|
||||
red = std::max(0.0f, std::min(1.0f, red));
|
||||
green = std::max(0.0f, std::min(1.0f, green));
|
||||
blue = std::max(0.0f, std::min(1.0f, blue));
|
||||
|
||||
const float y = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
|
||||
const float cb = (blue - y) / 1.8556f + 0.5f;
|
||||
const float cr = (red - y) / 1.5748f + 0.5f;
|
||||
|
||||
V210CodeValues values;
|
||||
values.y = Clamp10(static_cast<int>(std::lround(64.0f + y * 876.0f)), 64, 940);
|
||||
values.cb = Clamp10(static_cast<int>(std::lround(64.0f + cb * 896.0f)), 64, 960);
|
||||
values.cr = Clamp10(static_cast<int>(std::lround(64.0f + cr * 896.0f)), 64, 960);
|
||||
return values;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block)
|
||||
{
|
||||
std::array<uint8_t, 16> bytes = {};
|
||||
StoreWord(bytes, 0, MakeV210Word(block.cb[0], block.y[0], block.cr[0]));
|
||||
StoreWord(bytes, 1, MakeV210Word(block.y[1], block.cb[1], block.y[2]));
|
||||
StoreWord(bytes, 2, MakeV210Word(block.cr[1], block.y[3], block.cb[2]));
|
||||
StoreWord(bytes, 3, MakeV210Word(block.y[4], block.cr[2], block.y[5]));
|
||||
return bytes;
|
||||
}
|
||||
|
||||
V210SixPixelBlock UnpackV210Block(const std::array<uint8_t, 16>& bytes)
|
||||
{
|
||||
const uint32_t word0 = LoadWord(bytes, 0);
|
||||
const uint32_t word1 = LoadWord(bytes, 1);
|
||||
const uint32_t word2 = LoadWord(bytes, 2);
|
||||
const uint32_t word3 = LoadWord(bytes, 3);
|
||||
|
||||
V210SixPixelBlock block;
|
||||
block.cb[0] = Component(word0, 0);
|
||||
block.y[0] = Component(word0, 1);
|
||||
block.cr[0] = Component(word0, 2);
|
||||
block.y[1] = Component(word1, 0);
|
||||
block.cb[1] = Component(word1, 1);
|
||||
block.y[2] = Component(word1, 2);
|
||||
block.cr[1] = Component(word2, 0);
|
||||
block.y[3] = Component(word2, 1);
|
||||
block.cb[2] = Component(word2, 2);
|
||||
block.y[4] = Component(word3, 0);
|
||||
block.cr[2] = Component(word3, 1);
|
||||
block.y[5] = Component(word3, 2);
|
||||
return block;
|
||||
}
|
||||
39
src/video/VideoIOFormat.h
Normal file
39
src/video/VideoIOFormat.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
enum class VideoIOPixelFormat
|
||||
{
|
||||
Uyvy8,
|
||||
V210,
|
||||
Yuva10,
|
||||
Bgra8
|
||||
};
|
||||
|
||||
struct V210CodeValues
|
||||
{
|
||||
uint16_t y = 64;
|
||||
uint16_t cb = 512;
|
||||
uint16_t cr = 512;
|
||||
};
|
||||
|
||||
struct V210SixPixelBlock
|
||||
{
|
||||
std::array<uint16_t, 6> y = {};
|
||||
std::array<uint16_t, 3> cb = {};
|
||||
std::array<uint16_t, 3> cr = {};
|
||||
};
|
||||
|
||||
const char* VideoIOPixelFormatName(VideoIOPixelFormat format);
|
||||
bool VideoIOPixelFormatIsTenBit(VideoIOPixelFormat format);
|
||||
VideoIOPixelFormat ChoosePreferredVideoIOFormat(bool tenBitSupported);
|
||||
unsigned VideoIOBytesPerPixel(VideoIOPixelFormat format);
|
||||
unsigned VideoIORowBytes(VideoIOPixelFormat format, unsigned frameWidth);
|
||||
unsigned PackedTextureWidthFromRowBytes(unsigned rowBytes);
|
||||
unsigned MinimumV210RowBytes(unsigned frameWidth);
|
||||
unsigned MinimumYuva10RowBytes(unsigned frameWidth);
|
||||
unsigned ActiveV210WordsForWidth(unsigned frameWidth);
|
||||
V210CodeValues Rec709RgbToLegalV210(float red, float green, float blue);
|
||||
std::array<uint8_t, 16> PackV210Block(const V210SixPixelBlock& block);
|
||||
V210SixPixelBlock UnpackV210Block(const std::array<uint8_t, 16>& bytes);
|
||||
164
src/video/VideoIOTypes.h
Normal file
164
src/video/VideoIOTypes.h
Normal file
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
|
||||
#include "DeckLinkDisplayMode.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
enum class VideoIOBackend
|
||||
{
|
||||
DeckLink
|
||||
};
|
||||
|
||||
enum class VideoIOCompletionResult
|
||||
{
|
||||
Completed,
|
||||
DisplayedLate,
|
||||
Dropped,
|
||||
Flushed,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct VideoIOConfig
|
||||
{
|
||||
VideoFormatSelection videoModes;
|
||||
bool externalKeyingEnabled = false;
|
||||
bool preferTenBit = true;
|
||||
};
|
||||
|
||||
struct VideoIOState
|
||||
{
|
||||
FrameSize inputFrameSize;
|
||||
FrameSize outputFrameSize;
|
||||
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
VideoIOPixelFormat outputPixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
unsigned inputFrameRowBytes = 0;
|
||||
unsigned outputFrameRowBytes = 0;
|
||||
unsigned captureTextureWidth = 0;
|
||||
unsigned outputPackTextureWidth = 0;
|
||||
std::string inputDisplayModeName = "1080p59.94";
|
||||
std::string outputDisplayModeName = "1080p59.94";
|
||||
std::string outputModelName;
|
||||
std::string statusMessage;
|
||||
std::string formatStatusMessage;
|
||||
bool hasInputDevice = false;
|
||||
bool hasInputSource = false;
|
||||
bool supportsInternalKeying = false;
|
||||
bool supportsExternalKeying = false;
|
||||
bool keyerInterfaceAvailable = false;
|
||||
bool externalKeyingActive = false;
|
||||
double frameBudgetMilliseconds = 0.0;
|
||||
bool actualDeckLinkBufferedFramesAvailable = false;
|
||||
uint64_t actualDeckLinkBufferedFrames = 0;
|
||||
double deckLinkScheduleCallMilliseconds = 0.0;
|
||||
uint64_t deckLinkScheduleFailureCount = 0;
|
||||
bool deckLinkScheduleLeadAvailable = false;
|
||||
int64_t deckLinkPlaybackStreamTime = 0;
|
||||
uint64_t deckLinkPlaybackFrameIndex = 0;
|
||||
uint64_t deckLinkNextScheduleFrameIndex = 0;
|
||||
int64_t deckLinkScheduleLeadFrames = 0;
|
||||
uint64_t deckLinkScheduleRealignmentCount = 0;
|
||||
};
|
||||
|
||||
struct VideoIOFrame
|
||||
{
|
||||
void* bytes = nullptr;
|
||||
long rowBytes = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Uyvy8;
|
||||
bool hasNoInputSource = false;
|
||||
};
|
||||
|
||||
struct VideoIOOutputFrame
|
||||
{
|
||||
void* bytes = nullptr;
|
||||
long rowBytes = 0;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
void* nativeFrame = nullptr;
|
||||
void* nativeBuffer = nullptr;
|
||||
};
|
||||
|
||||
struct VideoIOCompletion
|
||||
{
|
||||
VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
|
||||
void* outputFrameBuffer = nullptr;
|
||||
};
|
||||
|
||||
struct VideoIOScheduleTime
|
||||
{
|
||||
int64_t streamTime = 0;
|
||||
int64_t duration = 0;
|
||||
int64_t timeScale = 0;
|
||||
uint64_t frameIndex = 0;
|
||||
};
|
||||
|
||||
struct VideoPlayoutRecoveryDecision
|
||||
{
|
||||
VideoIOCompletionResult result = VideoIOCompletionResult::Completed;
|
||||
uint64_t completedFrameIndex = 0;
|
||||
uint64_t scheduledFrameIndex = 0;
|
||||
uint64_t readyQueueDepth = 0;
|
||||
uint64_t scheduledLeadFrames = 0;
|
||||
uint64_t measuredLagFrames = 0;
|
||||
uint64_t catchUpFrames = 0;
|
||||
uint64_t lateStreak = 0;
|
||||
uint64_t dropStreak = 0;
|
||||
};
|
||||
|
||||
class VideoIODevice
|
||||
{
|
||||
public:
|
||||
using InputFrameCallback = std::function<void(const VideoIOFrame&)>;
|
||||
using OutputFrameCallback = std::function<void(const VideoIOCompletion&)>;
|
||||
|
||||
virtual ~VideoIODevice() = default;
|
||||
virtual void ReleaseResources() = 0;
|
||||
virtual bool DiscoverDevicesAndModes(const VideoFormatSelection& videoModes, std::string& error) = 0;
|
||||
virtual bool SelectPreferredFormats(const VideoFormatSelection& videoModes, bool outputAlphaRequired, std::string& error) = 0;
|
||||
virtual bool ConfigureInput(InputFrameCallback callback, const VideoFormat& inputVideoMode, std::string& error) = 0;
|
||||
virtual bool ConfigureOutput(OutputFrameCallback callback, const VideoFormat& outputVideoMode, bool externalKeyingEnabled, std::string& error) = 0;
|
||||
virtual bool PrepareOutputSchedule() = 0;
|
||||
virtual bool StartInputStreams() = 0;
|
||||
virtual bool StartScheduledPlayback() = 0;
|
||||
virtual bool Start() = 0;
|
||||
virtual bool Stop() = 0;
|
||||
virtual const VideoIOState& State() const = 0;
|
||||
virtual VideoIOState& MutableState() = 0;
|
||||
virtual bool BeginOutputFrame(VideoIOOutputFrame& frame) = 0;
|
||||
virtual void EndOutputFrame(VideoIOOutputFrame& frame) = 0;
|
||||
virtual bool ScheduleOutputFrame(const VideoIOOutputFrame& frame) = 0;
|
||||
virtual VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth) = 0;
|
||||
|
||||
bool HasInputDevice() const { return State().hasInputDevice; }
|
||||
bool HasInputSource() const { return State().hasInputSource; }
|
||||
bool InputOutputDimensionsDiffer() const { return State().inputFrameSize != State().outputFrameSize; }
|
||||
const FrameSize& InputFrameSize() const { return State().inputFrameSize; }
|
||||
const FrameSize& OutputFrameSize() const { return State().outputFrameSize; }
|
||||
unsigned InputFrameWidth() const { return State().inputFrameSize.width; }
|
||||
unsigned InputFrameHeight() const { return State().inputFrameSize.height; }
|
||||
unsigned OutputFrameWidth() const { return State().outputFrameSize.width; }
|
||||
unsigned OutputFrameHeight() const { return State().outputFrameSize.height; }
|
||||
VideoIOPixelFormat InputPixelFormat() const { return State().inputPixelFormat; }
|
||||
VideoIOPixelFormat OutputPixelFormat() const { return State().outputPixelFormat; }
|
||||
bool InputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().inputPixelFormat); }
|
||||
bool OutputIsTenBit() const { return VideoIOPixelFormatIsTenBit(State().outputPixelFormat); }
|
||||
unsigned InputFrameRowBytes() const { return State().inputFrameRowBytes; }
|
||||
unsigned OutputFrameRowBytes() const { return State().outputFrameRowBytes; }
|
||||
unsigned CaptureTextureWidth() const { return State().captureTextureWidth; }
|
||||
unsigned OutputPackTextureWidth() const { return State().outputPackTextureWidth; }
|
||||
const std::string& FormatStatusMessage() const { return State().formatStatusMessage; }
|
||||
const std::string& InputDisplayModeName() const { return State().inputDisplayModeName; }
|
||||
const std::string& OutputModelName() const { return State().outputModelName; }
|
||||
bool SupportsInternalKeying() const { return State().supportsInternalKeying; }
|
||||
bool SupportsExternalKeying() const { return State().supportsExternalKeying; }
|
||||
bool KeyerInterfaceAvailable() const { return State().keyerInterfaceAvailable; }
|
||||
bool ExternalKeyingActive() const { return State().externalKeyingActive; }
|
||||
const std::string& StatusMessage() const { return State().statusMessage; }
|
||||
double FrameBudgetMilliseconds() const { return State().frameBudgetMilliseconds; }
|
||||
void SetStatusMessage(const std::string& message) { MutableState().statusMessage = message; }
|
||||
};
|
||||
37
src/video/VideoPlayoutPolicy.h
Normal file
37
src/video/VideoPlayoutPolicy.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class VideoUnderrunBehavior
|
||||
{
|
||||
ReuseLastCompletedFrame,
|
||||
BlackFrame
|
||||
};
|
||||
|
||||
struct VideoPlayoutPolicy
|
||||
{
|
||||
unsigned outputFramePoolSize = 10;
|
||||
unsigned targetPrerollFrames = 4;
|
||||
unsigned targetReadyFrames = 2;
|
||||
unsigned maxReadyFrames = 4;
|
||||
unsigned minimumSpareDeviceFrames = 1;
|
||||
uint64_t lateOrDropCatchUpFrames = 0;
|
||||
VideoUnderrunBehavior underrunBehavior = VideoUnderrunBehavior::ReuseLastCompletedFrame;
|
||||
bool adaptiveHeadroomEnabled = false;
|
||||
};
|
||||
|
||||
inline VideoPlayoutPolicy NormalizeVideoPlayoutPolicy(VideoPlayoutPolicy policy)
|
||||
{
|
||||
if (policy.outputFramePoolSize == 0)
|
||||
policy.outputFramePoolSize = 1;
|
||||
if (policy.targetPrerollFrames == 0)
|
||||
policy.targetPrerollFrames = 1;
|
||||
if (policy.targetReadyFrames == 0)
|
||||
policy.targetReadyFrames = 1;
|
||||
if (policy.maxReadyFrames < policy.targetReadyFrames)
|
||||
policy.maxReadyFrames = policy.targetReadyFrames;
|
||||
const unsigned minimumOutputFramePoolSize = policy.targetPrerollFrames + policy.maxReadyFrames + policy.minimumSpareDeviceFrames;
|
||||
if (policy.outputFramePoolSize < minimumOutputFramePoolSize)
|
||||
policy.outputFramePoolSize = minimumOutputFramePoolSize;
|
||||
return policy;
|
||||
}
|
||||
108
src/video/VideoPlayoutScheduler.cpp
Normal file
108
src/video/VideoPlayoutScheduler.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "VideoPlayoutScheduler.h"
|
||||
|
||||
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale)
|
||||
{
|
||||
Configure(frameDuration, timeScale, VideoPlayoutPolicy());
|
||||
}
|
||||
|
||||
void VideoPlayoutScheduler::Configure(int64_t frameDuration, int64_t timeScale, const VideoPlayoutPolicy& policy)
|
||||
{
|
||||
mFrameDuration = frameDuration;
|
||||
mTimeScale = timeScale;
|
||||
mPolicy = NormalizeVideoPlayoutPolicy(policy);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void VideoPlayoutScheduler::Reset()
|
||||
{
|
||||
mScheduledFrameIndex = 0;
|
||||
mCompletedFrameIndex = 0;
|
||||
mLateStreak = 0;
|
||||
mDropStreak = 0;
|
||||
}
|
||||
|
||||
VideoIOScheduleTime VideoPlayoutScheduler::NextScheduleTime()
|
||||
{
|
||||
VideoIOScheduleTime time;
|
||||
time.streamTime = static_cast<int64_t>(mScheduledFrameIndex) * mFrameDuration;
|
||||
time.duration = mFrameDuration;
|
||||
time.timeScale = mTimeScale;
|
||||
time.frameIndex = mScheduledFrameIndex;
|
||||
++mScheduledFrameIndex;
|
||||
return time;
|
||||
}
|
||||
|
||||
void VideoPlayoutScheduler::AlignNextScheduleTimeToPlayback(int64_t streamTime, uint64_t leadFrames)
|
||||
{
|
||||
if (mFrameDuration <= 0 || streamTime < 0)
|
||||
return;
|
||||
|
||||
const uint64_t playbackFrameIndex = static_cast<uint64_t>(streamTime / mFrameDuration);
|
||||
const uint64_t minimumScheduleIndex = playbackFrameIndex + leadFrames;
|
||||
if (minimumScheduleIndex > mScheduledFrameIndex)
|
||||
mScheduledFrameIndex = minimumScheduleIndex;
|
||||
}
|
||||
|
||||
VideoPlayoutRecoveryDecision VideoPlayoutScheduler::AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth)
|
||||
{
|
||||
++mCompletedFrameIndex;
|
||||
if (result == VideoIOCompletionResult::DisplayedLate)
|
||||
++mLateStreak;
|
||||
else
|
||||
mLateStreak = 0;
|
||||
if (result == VideoIOCompletionResult::Dropped)
|
||||
++mDropStreak;
|
||||
else
|
||||
mDropStreak = 0;
|
||||
|
||||
const uint64_t measuredLagFrames = MeasureLag(result, readyQueueDepth);
|
||||
const uint64_t catchUpFrames = measuredLagFrames < mPolicy.lateOrDropCatchUpFrames
|
||||
? measuredLagFrames
|
||||
: mPolicy.lateOrDropCatchUpFrames;
|
||||
if (catchUpFrames > 0)
|
||||
mScheduledFrameIndex += catchUpFrames;
|
||||
|
||||
VideoPlayoutRecoveryDecision decision;
|
||||
decision.result = result;
|
||||
decision.completedFrameIndex = mCompletedFrameIndex;
|
||||
decision.scheduledFrameIndex = mScheduledFrameIndex;
|
||||
decision.readyQueueDepth = readyQueueDepth;
|
||||
decision.scheduledLeadFrames = mScheduledFrameIndex > mCompletedFrameIndex
|
||||
? mScheduledFrameIndex - mCompletedFrameIndex
|
||||
: 0;
|
||||
decision.measuredLagFrames = measuredLagFrames;
|
||||
decision.catchUpFrames = catchUpFrames;
|
||||
decision.lateStreak = mLateStreak;
|
||||
decision.dropStreak = mDropStreak;
|
||||
return decision;
|
||||
}
|
||||
|
||||
double VideoPlayoutScheduler::FrameBudgetMilliseconds() const
|
||||
{
|
||||
return mTimeScale != 0
|
||||
? (static_cast<double>(mFrameDuration) * 1000.0) / static_cast<double>(mTimeScale)
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
uint64_t VideoPlayoutScheduler::MeasureLag(VideoIOCompletionResult result, uint64_t readyQueueDepth) const
|
||||
{
|
||||
if (result != VideoIOCompletionResult::DisplayedLate && result != VideoIOCompletionResult::Dropped)
|
||||
return 0;
|
||||
|
||||
uint64_t lagFrames = 1;
|
||||
if (result == VideoIOCompletionResult::DisplayedLate && mLateStreak > lagFrames)
|
||||
lagFrames = mLateStreak;
|
||||
if (result == VideoIOCompletionResult::Dropped && mDropStreak * 2 > lagFrames)
|
||||
lagFrames = mDropStreak * 2;
|
||||
|
||||
if (mCompletedFrameIndex >= mScheduledFrameIndex)
|
||||
{
|
||||
const uint64_t scheduleLagFrames = mCompletedFrameIndex - mScheduledFrameIndex + 1;
|
||||
if (scheduleLagFrames > lagFrames)
|
||||
lagFrames = scheduleLagFrames;
|
||||
}
|
||||
if (readyQueueDepth < mPolicy.targetReadyFrames && mPolicy.targetReadyFrames - readyQueueDepth > lagFrames)
|
||||
lagFrames = mPolicy.targetReadyFrames - readyQueueDepth;
|
||||
|
||||
return lagFrames;
|
||||
}
|
||||
36
src/video/VideoPlayoutScheduler.h
Normal file
36
src/video/VideoPlayoutScheduler.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
#include "VideoPlayoutPolicy.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class VideoPlayoutScheduler
|
||||
{
|
||||
public:
|
||||
void Configure(int64_t frameDuration, int64_t timeScale);
|
||||
void Configure(int64_t frameDuration, int64_t timeScale, const VideoPlayoutPolicy& policy);
|
||||
void Reset();
|
||||
VideoIOScheduleTime NextScheduleTime();
|
||||
void AlignNextScheduleTimeToPlayback(int64_t streamTime, uint64_t leadFrames);
|
||||
VideoPlayoutRecoveryDecision AccountForCompletionResult(VideoIOCompletionResult result, uint64_t readyQueueDepth = 0);
|
||||
double FrameBudgetMilliseconds() const;
|
||||
uint64_t ScheduledFrameIndex() const { return mScheduledFrameIndex; }
|
||||
uint64_t CompletedFrameIndex() const { return mCompletedFrameIndex; }
|
||||
int64_t FrameDuration() const { return mFrameDuration; }
|
||||
uint64_t LateStreak() const { return mLateStreak; }
|
||||
uint64_t DropStreak() const { return mDropStreak; }
|
||||
int64_t TimeScale() const { return mTimeScale; }
|
||||
const VideoPlayoutPolicy& Policy() const { return mPolicy; }
|
||||
|
||||
private:
|
||||
uint64_t MeasureLag(VideoIOCompletionResult result, uint64_t readyQueueDepth) const;
|
||||
|
||||
int64_t mFrameDuration = 0;
|
||||
int64_t mTimeScale = 0;
|
||||
uint64_t mScheduledFrameIndex = 0;
|
||||
uint64_t mCompletedFrameIndex = 0;
|
||||
uint64_t mLateStreak = 0;
|
||||
uint64_t mDropStreak = 0;
|
||||
VideoPlayoutPolicy mPolicy;
|
||||
};
|
||||
97
tests/CMakeLists.txt
Normal file
97
tests/CMakeLists.txt
Normal file
@@ -0,0 +1,97 @@
|
||||
add_video_shader_test(RenderCadenceCompositorClockTests
|
||||
"${SRC_DIR}/render/RenderCadenceClock.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorClockTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RenderCadenceCompositorJsonWriterTests
|
||||
"${SRC_DIR}/json/JsonWriter.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorJsonWriterTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RenderCadenceCompositorLoggerTests
|
||||
"${SRC_DIR}/logging/Logger.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorLoggerTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RenderCadenceCompositorTelemetryTests
|
||||
"${SRC_DIR}/json/JsonWriter.cpp"
|
||||
"${TEST_DIR}/RenderCadenceCompositorTelemetryTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RuntimeJsonTests
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${TEST_DIR}/RuntimeJsonTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RuntimeParameterUtilsTests
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeParameterUtils.cpp"
|
||||
"${TEST_DIR}/RuntimeParameterUtilsTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(ShaderPackageRegistryTests
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/ShaderPackageRegistryTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(ShaderSlangValidationTests
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/shader/ShaderCompiler.cpp"
|
||||
"${SRC_DIR}/shader/ShaderPackageRegistry.cpp"
|
||||
"${TEST_DIR}/ShaderSlangValidationTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(Std140BufferTests
|
||||
"${TEST_DIR}/Std140BufferTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(VideoIOFormatTests
|
||||
"${SRC_DIR}/video/DeckLinkVideoIOFormat.cpp"
|
||||
"${SRC_DIR}/video/VideoIOFormat.cpp"
|
||||
"${TEST_DIR}/VideoIOFormatTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(VideoPlayoutSchedulerTests
|
||||
"${SRC_DIR}/video/VideoPlayoutScheduler.cpp"
|
||||
"${TEST_DIR}/VideoPlayoutSchedulerTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(OutputProductionControllerTests
|
||||
"${SRC_DIR}/video/OutputProductionController.cpp"
|
||||
"${TEST_DIR}/OutputProductionControllerTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RenderOutputQueueTests
|
||||
"${SRC_DIR}/video/RenderOutputQueue.cpp"
|
||||
"${TEST_DIR}/RenderOutputQueueTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RenderCadenceControllerTests
|
||||
"${SRC_DIR}/video/RenderCadenceController.cpp"
|
||||
"${TEST_DIR}/RenderCadenceControllerTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(SystemOutputFramePoolTests
|
||||
"${SRC_DIR}/video/SystemOutputFramePool.cpp"
|
||||
"${SRC_DIR}/video/VideoIOFormat.cpp"
|
||||
"${TEST_DIR}/SystemOutputFramePoolTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(VideoBackendLifecycleTests
|
||||
"${SRC_DIR}/video/VideoBackendLifecycle.cpp"
|
||||
"${TEST_DIR}/VideoBackendLifecycleTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(VideoIODeviceFakeTests
|
||||
"${SRC_DIR}/video/VideoIOFormat.cpp"
|
||||
"${TEST_DIR}/VideoIODeviceFakeTests.cpp"
|
||||
)
|
||||
|
||||
set_tests_properties(RenderCadenceCompositorLoggerTests PROPERTIES
|
||||
ENVIRONMENT "VIDEO_SHADER_TEST_LOG_TO_CONSOLE=0"
|
||||
)
|
||||
|
||||
set_tests_properties(ShaderSlangValidationTests PROPERTIES
|
||||
ENVIRONMENT "SLANG_ROOT=${SLANG_ROOT}"
|
||||
)
|
||||
Reference in New Issue
Block a user