This commit is contained in:
2026-05-02 16:40:21 +10:00
parent 8d01ea4a3c
commit 1a4c33b9dc
23 changed files with 3725 additions and 401 deletions

View File

@@ -0,0 +1,454 @@
#include "stdafx.h"
#include "ControlServer.h"
#include "RuntimeJson.h"
#include <Wincrypt.h>
#include <ws2tcpip.h>
#include <algorithm>
#include <fstream>
#include <sstream>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "Advapi32.lib")
namespace
{
bool InitializeWinsock(std::string& error)
{
WSADATA wsaData = {};
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0)
{
error = "WSAStartup failed.";
return false;
}
return true;
}
std::string ToLower(std::string text)
{
std::transform(text.begin(), text.end(), text.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
return text;
}
}
ControlServer::ControlServer()
: mListenSocket(INVALID_SOCKET), mPort(0), mRunning(false)
{
}
ControlServer::~ControlServer()
{
Stop();
}
bool ControlServer::Start(const std::filesystem::path& uiRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error)
{
mUiRoot = uiRoot;
mCallbacks = callbacks;
if (!InitializeWinsock(error))
return false;
mListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (mListenSocket == INVALID_SOCKET)
{
error = "Could not create listening socket.";
return false;
}
u_long nonBlocking = 1;
ioctlsocket(mListenSocket, FIONBIO, &nonBlocking);
sockaddr_in address = {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
bool bound = false;
for (unsigned short offset = 0; offset < 20; ++offset)
{
address.sin_port = htons(static_cast<u_short>(preferredPort + offset));
if (bind(mListenSocket, reinterpret_cast<sockaddr*>(&address), sizeof(address)) == 0)
{
mPort = preferredPort + offset;
bound = true;
break;
}
}
if (!bound)
{
error = "Could not bind the local control server to any port in the preferred range.";
closesocket(mListenSocket);
mListenSocket = INVALID_SOCKET;
return false;
}
if (listen(mListenSocket, SOMAXCONN) != 0)
{
error = "Could not start listening on the local control server socket.";
closesocket(mListenSocket);
mListenSocket = INVALID_SOCKET;
return false;
}
mRunning = true;
mThread = std::thread(&ControlServer::ServerLoop, this);
return true;
}
void ControlServer::Stop()
{
const bool wasActive = mRunning || mListenSocket != INVALID_SOCKET || mThread.joinable();
mRunning = false;
{
std::lock_guard<std::mutex> lock(mMutex);
for (ClientConnection& client : mClients)
{
if (client.socket != INVALID_SOCKET)
{
closesocket(client.socket);
client.socket = INVALID_SOCKET;
}
}
mClients.clear();
}
if (mListenSocket != INVALID_SOCKET)
{
closesocket(mListenSocket);
mListenSocket = INVALID_SOCKET;
}
if (mThread.joinable())
mThread.join();
if (wasActive)
WSACleanup();
}
void ControlServer::BroadcastState()
{
std::lock_guard<std::mutex> lock(mMutex);
BroadcastStateLocked();
}
void ControlServer::ServerLoop()
{
while (mRunning)
{
TryAcceptClient();
Sleep(25);
}
}
bool ControlServer::HandleHttpClient(SOCKET clientSocket)
{
std::string request;
char buffer[8192];
int received = recv(clientSocket, buffer, sizeof(buffer), 0);
if (received <= 0)
return false;
request.assign(buffer, buffer + received);
return HandleHttpRequest(clientSocket, request);
}
bool ControlServer::TryAcceptClient()
{
sockaddr_in clientAddress = {};
int addressSize = sizeof(clientAddress);
SOCKET clientSocket = accept(mListenSocket, reinterpret_cast<sockaddr*>(&clientAddress), &addressSize);
if (clientSocket == INVALID_SOCKET)
return false;
bool handled = HandleHttpClient(clientSocket);
if (!handled)
closesocket(clientSocket);
return handled;
}
bool ControlServer::SendHttpResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& body)
{
std::ostringstream response;
response << "HTTP/1.1 " << status << "\r\n";
response << "Content-Type: " << contentType << "\r\n";
response << "Content-Length: " << body.size() << "\r\n";
response << "Connection: close\r\n\r\n";
response << body;
const std::string payload = response.str();
return send(clientSocket, payload.c_str(), static_cast<int>(payload.size()), 0) == static_cast<int>(payload.size());
}
bool ControlServer::HandleHttpRequest(SOCKET clientSocket, const std::string& request)
{
const std::string method = GetRequestMethod(request);
const std::string path = GetRequestPath(request);
if (ToLower(GetHeaderValue(request, "Upgrade")) == "websocket")
return HandleWebSocketUpgrade(clientSocket, request);
if (method == "GET")
{
if (path == "/" || path == "/index.html")
{
std::string contentType;
std::string body = LoadUiAsset("index.html", contentType);
SendHttpResponse(clientSocket, "200 OK", contentType, body);
closesocket(clientSocket);
return true;
}
if (path == "/app.js" || path == "/styles.css")
{
std::string contentType;
std::string body = LoadUiAsset(path.substr(1), contentType);
SendHttpResponse(clientSocket, "200 OK", contentType, body);
closesocket(clientSocket);
return true;
}
if (path == "/api/state")
{
SendHttpResponse(clientSocket, "200 OK", "application/json", mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}");
closesocket(clientSocket);
return true;
}
}
else if (method == "POST")
{
std::string body = GetRequestBody(request);
JsonValue root;
std::string parseError;
if (!ParseJson(body, root, parseError))
{
SendHttpResponse(clientSocket, "400 Bad Request", "application/json", BuildJsonResponse(false, parseError));
closesocket(clientSocket);
return true;
}
bool success = false;
std::string actionError;
if (path == "/api/select-shader")
{
const JsonValue* shaderId = root.find("shaderId");
success = shaderId && mCallbacks.selectShader && mCallbacks.selectShader(shaderId->asString(), actionError);
}
else if (path == "/api/update-parameter")
{
const JsonValue* shaderId = root.find("shaderId");
const JsonValue* parameterId = root.find("parameterId");
const JsonValue* value = root.find("value");
if (shaderId && parameterId && value && mCallbacks.updateParameter)
success = mCallbacks.updateParameter(shaderId->asString(), parameterId->asString(), SerializeJson(*value, false), actionError);
}
else if (path == "/api/set-bypass")
{
const JsonValue* bypass = root.find("bypass");
if (bypass && mCallbacks.setBypass)
success = mCallbacks.setBypass(bypass->asBoolean(), actionError);
}
else if (path == "/api/set-mix")
{
const JsonValue* mixAmount = root.find("mixAmount");
if (mixAmount && mCallbacks.setMixAmount)
success = mCallbacks.setMixAmount(mixAmount->asNumber(), actionError);
}
else if (path == "/api/reload")
{
if (mCallbacks.reloadShader)
success = mCallbacks.reloadShader(actionError);
}
SendHttpResponse(clientSocket, success ? "200 OK" : "400 Bad Request", "application/json", BuildJsonResponse(success, actionError));
closesocket(clientSocket);
if (success)
BroadcastState();
return true;
}
SendHttpResponse(clientSocket, "404 Not Found", "text/plain", "Not Found");
closesocket(clientSocket);
return true;
}
bool ControlServer::HandleWebSocketUpgrade(SOCKET clientSocket, const std::string& request)
{
const std::string clientKey = GetHeaderValue(request, "Sec-WebSocket-Key");
if (clientKey.empty())
{
SendHttpResponse(clientSocket, "400 Bad Request", "text/plain", "Missing Sec-WebSocket-Key");
closesocket(clientSocket);
return true;
}
std::ostringstream response;
response << "HTTP/1.1 101 Switching Protocols\r\n";
response << "Upgrade: websocket\r\n";
response << "Connection: Upgrade\r\n";
response << "Sec-WebSocket-Accept: " << ComputeWebSocketAcceptKey(clientKey) << "\r\n\r\n";
const std::string payload = response.str();
send(clientSocket, payload.c_str(), static_cast<int>(payload.size()), 0);
{
std::lock_guard<std::mutex> lock(mMutex);
ClientConnection client;
client.socket = clientSocket;
client.websocket = true;
mClients.push_back(client);
BroadcastStateLocked();
}
return true;
}
bool ControlServer::SendWebSocketText(SOCKET clientSocket, const std::string& payload)
{
std::string frame;
frame.push_back(static_cast<char>(0x81));
if (payload.size() <= 125)
{
frame.push_back(static_cast<char>(payload.size()));
}
else if (payload.size() <= 65535)
{
frame.push_back(126);
frame.push_back(static_cast<char>((payload.size() >> 8) & 0xFF));
frame.push_back(static_cast<char>(payload.size() & 0xFF));
}
else
{
frame.push_back(127);
for (int shift = 56; shift >= 0; shift -= 8)
frame.push_back(static_cast<char>((payload.size() >> shift) & 0xFF));
}
frame.append(payload);
return send(clientSocket, frame.data(), static_cast<int>(frame.size()), 0) == static_cast<int>(frame.size());
}
void ControlServer::BroadcastStateLocked()
{
const std::string stateMessage = mCallbacks.getStateJson ? mCallbacks.getStateJson() : "{}";
for (auto it = mClients.begin(); it != mClients.end();)
{
if (!SendWebSocketText(it->socket, stateMessage))
{
closesocket(it->socket);
it = mClients.erase(it);
}
else
{
++it;
}
}
}
std::string ControlServer::LoadUiAsset(const std::string& relativePath, std::string& contentType) const
{
const std::filesystem::path assetPath = mUiRoot / relativePath;
std::ifstream input(assetPath, std::ios::binary);
if (!input)
return "<!doctype html><title>Missing UI asset</title><p>UI asset missing.</p>";
if (assetPath.extension() == ".js")
contentType = "text/javascript";
else if (assetPath.extension() == ".css")
contentType = "text/css";
else
contentType = "text/html";
std::ostringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
std::string ControlServer::BuildJsonResponse(bool success, const std::string& error) const
{
JsonValue response = JsonValue::MakeObject();
response.set("ok", JsonValue(success));
if (!error.empty())
response.set("error", JsonValue(error));
return SerializeJson(response, false);
}
std::string ControlServer::Base64Encode(const unsigned char* data, DWORD dataLength)
{
DWORD outputLength = 0;
CryptBinaryToStringA(data, dataLength, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &outputLength);
std::string encoded(outputLength, '\0');
CryptBinaryToStringA(data, dataLength, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, &encoded[0], &outputLength);
if (!encoded.empty() && encoded.back() == '\0')
encoded.pop_back();
return encoded;
}
std::string ControlServer::ComputeWebSocketAcceptKey(const std::string& clientKey)
{
const std::string combined = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
HCRYPTPROV provider = 0;
HCRYPTHASH hash = 0;
BYTE digest[20] = {};
DWORD digestLength = sizeof(digest);
CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
CryptCreateHash(provider, CALG_SHA1, 0, 0, &hash);
CryptHashData(hash, reinterpret_cast<const BYTE*>(combined.data()), static_cast<DWORD>(combined.size()), 0);
CryptGetHashParam(hash, HP_HASHVAL, digest, &digestLength, 0);
if (hash)
CryptDestroyHash(hash);
if (provider)
CryptReleaseContext(provider, 0);
return Base64Encode(digest, digestLength);
}
std::string ControlServer::GetHeaderValue(const std::string& request, const std::string& headerName)
{
const std::string lowerRequest = ToLower(request);
const std::string lowerHeaderName = ToLower(headerName) + ":";
const std::size_t start = lowerRequest.find(lowerHeaderName);
if (start == std::string::npos)
return std::string();
const std::size_t valueStart = start + lowerHeaderName.size();
const std::size_t lineEnd = request.find("\r\n", valueStart);
if (lineEnd == std::string::npos)
return std::string();
std::string value = request.substr(valueStart, lineEnd - valueStart);
const std::size_t first = value.find_first_not_of(" \t");
const std::size_t last = value.find_last_not_of(" \t");
return first == std::string::npos ? std::string() : value.substr(first, last - first + 1);
}
std::string ControlServer::GetRequestPath(const std::string& request)
{
const std::size_t methodEnd = request.find(' ');
if (methodEnd == std::string::npos)
return "/";
const std::size_t pathEnd = request.find(' ', methodEnd + 1);
if (pathEnd == std::string::npos)
return "/";
return request.substr(methodEnd + 1, pathEnd - methodEnd - 1);
}
std::string ControlServer::GetRequestMethod(const std::string& request)
{
const std::size_t methodEnd = request.find(' ');
return methodEnd == std::string::npos ? std::string() : request.substr(0, methodEnd);
}
std::string ControlServer::GetRequestBody(const std::string& request)
{
const std::size_t separator = request.find("\r\n\r\n");
if (separator == std::string::npos)
return std::string();
return request.substr(separator + 4);
}

View File

@@ -0,0 +1,68 @@
#pragma once
#include <winsock2.h>
#include <atomic>
#include <filesystem>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
class ControlServer
{
public:
struct Callbacks
{
std::function<std::string()> getStateJson;
std::function<bool(const std::string&, std::string&)> selectShader;
std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)> updateParameter;
std::function<bool(bool, std::string&)> setBypass;
std::function<bool(double, std::string&)> setMixAmount;
std::function<bool(std::string&)> reloadShader;
};
ControlServer();
~ControlServer();
bool Start(const std::filesystem::path& uiRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
void Stop();
void BroadcastState();
unsigned short GetPort() const { return mPort; }
private:
struct ClientConnection
{
SOCKET socket = INVALID_SOCKET;
bool websocket = false;
};
void ServerLoop();
bool HandleHttpClient(SOCKET clientSocket);
bool TryAcceptClient();
bool SendHttpResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& body);
bool HandleHttpRequest(SOCKET clientSocket, const std::string& request);
bool HandleWebSocketUpgrade(SOCKET clientSocket, const std::string& request);
bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
void BroadcastStateLocked();
std::string LoadUiAsset(const std::string& relativePath, std::string& contentType) const;
std::string BuildJsonResponse(bool success, const std::string& error = std::string()) const;
static std::string Base64Encode(const unsigned char* data, DWORD dataLength);
static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
static std::string GetHeaderValue(const std::string& request, const std::string& headerName);
static std::string GetRequestPath(const std::string& request);
static std::string GetRequestMethod(const std::string& request);
static std::string GetRequestBody(const std::string& request);
private:
std::filesystem::path mUiRoot;
Callbacks mCallbacks;
SOCKET mListenSocket;
unsigned short mPort;
std::thread mThread;
std::atomic<bool> mRunning;
mutable std::mutex mMutex;
std::vector<ClientConnection> mClients;
};

View File

@@ -44,17 +44,17 @@
#include "GLExtensions.h"
PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT;
PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT;
PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT;
PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT;
PFNGLDELETERENDERBUFFERSEXTPROC glDeleteRenderbuffersEXT;
PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT;
PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT;
PFNGLBLITFRAMEBUFFEREXTPROC glBlitFramebufferEXT;
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;
@@ -62,6 +62,12 @@ PFNGLGENBUFFERSPROC glGenBuffers;
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLBUFFERDATAPROC glBufferData;
PFNGLBUFFERSUBDATAPROC glBufferSubData;
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
PFNGLACTIVETEXTUREPROC glActiveTexture;
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
PFNGLCREATESHADERPROC glCreateShader;
PFNGLDELETESHADERPROC glDeleteShader;
PFNGLDELETEPROGRAMPROC glDeleteProgram;
@@ -78,20 +84,44 @@ PFNGLUSEPROGRAMPROC glUseProgram;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
PFNGLUNIFORM1IPROC glUniform1i;
PFNGLUNIFORM1FPROC glUniform1f;
PFNGLUNIFORM2FPROC glUniform2f;
PFNGLUNIFORM4FPROC glUniform4f;
bool ResolveGLExtensions()
{
glGenFramebuffersEXT = (PFNGLGENFRAMEBUFFERSEXTPROC) wglGetProcAddress("glGenFramebuffersEXT");
glGenRenderbuffersEXT = (PFNGLGENRENDERBUFFERSEXTPROC) wglGetProcAddress("glGenRenderbuffersEXT");
glBindRenderbufferEXT = (PFNGLBINDRENDERBUFFEREXTPROC) wglGetProcAddress("glBindRenderbufferEXT");
glRenderbufferStorageEXT = (PFNGLRENDERBUFFERSTORAGEEXTPROC) wglGetProcAddress("glRenderbufferStorageEXT");
glDeleteFramebuffersEXT = (PFNGLDELETEFRAMEBUFFERSEXTPROC) wglGetProcAddress("glDeleteFramebuffersEXT");
glDeleteRenderbuffersEXT = (PFNGLDELETERENDERBUFFERSEXTPROC) wglGetProcAddress("glDeleteRenderbuffersEXT");
glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) wglGetProcAddress("glBindFramebufferEXT");
glFramebufferTexture2DEXT = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) wglGetProcAddress("glFramebufferTexture2DEXT");
glFramebufferRenderbufferEXT = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) wglGetProcAddress("glFramebufferRenderbufferEXT");
glCheckFramebufferStatusEXT = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) wglGetProcAddress("glCheckFramebufferStatusEXT");
glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC) wglGetProcAddress("glBlitFramebufferEXT");
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");
@@ -99,6 +129,12 @@ bool ResolveGLExtensions()
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
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");
@@ -115,18 +151,20 @@ bool ResolveGLExtensions()
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress("glGetUniformLocation");
glUniform1i = (PFNGLUNIFORM1IPROC) wglGetProcAddress("glUniform1i");
glUniform1f = (PFNGLUNIFORM1FPROC) wglGetProcAddress("glUniform1f");
glUniform2f = (PFNGLUNIFORM2FPROC) wglGetProcAddress("glUniform2f");
glUniform4f = (PFNGLUNIFORM4FPROC) wglGetProcAddress("glUniform4f");
return glGenFramebuffersEXT
&& glGenRenderbuffersEXT
&& glBindRenderbufferEXT
&& glRenderbufferStorageEXT
&& glDeleteFramebuffersEXT
&& glDeleteRenderbuffersEXT
&& glBindFramebufferEXT
&& glFramebufferTexture2DEXT
&& glFramebufferRenderbufferEXT
&& glCheckFramebufferStatusEXT
&& glBlitFramebufferEXT
return glGenFramebuffers
&& glGenRenderbuffers
&& glBindRenderbuffer
&& glRenderbufferStorage
&& glDeleteFramebuffers
&& glDeleteRenderbuffers
&& glBindFramebuffer
&& glFramebufferTexture2D
&& glFramebufferRenderbuffer
&& glCheckFramebufferStatus
&& glBlitFramebuffer
&& glFenceSync
&& glClientWaitSync
&& glDeleteSync
@@ -134,6 +172,12 @@ bool ResolveGLExtensions()
&& glDeleteBuffers
&& glBindBuffer
&& glBufferData
&& glBufferSubData
&& glBindBufferBase
&& glActiveTexture
&& glGenVertexArrays
&& glDeleteVertexArrays
&& glBindVertexArray
&& glCreateShader
&& glDeleteShader
&& glDeleteProgram
@@ -150,5 +194,7 @@ bool ResolveGLExtensions()
&& glGetUniformLocation
&& glUniform1i
&& glUniform1f
&& glUniform2f
&& glUniform4f
;
}

View File

@@ -58,6 +58,8 @@
#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_ARRAY_BUFFER 0x8892
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
@@ -71,6 +73,12 @@
#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
@@ -102,32 +110,40 @@ typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, co
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 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 PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer);
typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers);
typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers);
typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer);
typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers);
typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers);
typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target);
typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
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 PFNGLGENFRAMEBUFFERSEXTPROC glGenFramebuffersEXT;
extern PFNGLGENRENDERBUFFERSEXTPROC glGenRenderbuffersEXT;
extern PFNGLBINDRENDERBUFFEREXTPROC glBindRenderbufferEXT;
extern PFNGLRENDERBUFFERSTORAGEEXTPROC glRenderbufferStorageEXT;
extern PFNGLDELETEFRAMEBUFFERSEXTPROC glDeleteFramebuffersEXT;
extern PFNGLDELETERENDERBUFFERSEXTPROC glDeleteRenderbuffersEXT;
extern PFNGLBINDFRAMEBUFFEREXTPROC glBindFramebufferEXT;
extern PFNGLFRAMEBUFFERTEXTURE2DEXTPROC glFramebufferTexture2DEXT;
extern PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbufferEXT;
extern PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC glCheckFramebufferStatusEXT;
extern PFNGLBLITFRAMEBUFFEREXTPROC glBlitFramebufferEXT;
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;
@@ -135,6 +151,12 @@ extern PFNGLGENBUFFERSPROC glGenBuffers;
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
extern PFNGLBINDBUFFERPROC glBindBuffer;
extern PFNGLBUFFERDATAPROC glBufferData;
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;
@@ -151,6 +173,8 @@ extern PFNGLUSEPROGRAMPROC glUseProgram;
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
extern PFNGLUNIFORM1IPROC glUniform1i;
extern PFNGLUNIFORM1FPROC glUniform1f;
extern PFNGLUNIFORM2FPROC glUniform2f;
extern PFNGLUNIFORM4FPROC glUniform4f;
bool ResolveGLExtensions();

View File

@@ -46,11 +46,41 @@
#include "resource.h"
#include "OpenGLComposite.h"
#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
#endif
#ifndef WGL_CONTEXT_MINOR_VERSION_ARB
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
#endif
#ifndef WGL_CONTEXT_PROFILE_MASK_ARB
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
#endif
#ifndef WGL_CONTEXT_CORE_PROFILE_BIT_ARB
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#endif
#define MAX_LOADSTRING 100
// Declaration for Window procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hShareContext, const int* attribList);
void ShowUnhandledExceptionMessage(const char* prefix)
{
try
{
throw;
}
catch (const std::exception& exception)
{
std::string message = std::string(prefix) + "\n\n" + exception.what();
MessageBoxA(NULL, message.c_str(), "Unhandled exception", MB_OK | MB_ICONERROR);
}
catch (...)
{
MessageBoxA(NULL, prefix, "Unhandled exception", MB_OK | MB_ICONERROR);
}
}
// Select the pixel format for a given device context
void SetDCPixelFormat(HDC hDC)
@@ -82,6 +112,38 @@ void SetDCPixelFormat(HDC hDC)
SetPixelFormat(hDC, nPixelFormat, &pfd);
}
HGLRC CreateModernOpenGLContext(HDC hDC)
{
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB =
reinterpret_cast<PFNWGLCREATECONTEXTATTRIBSARBPROC>(wglGetProcAddress("wglCreateContextAttribsARB"));
if (!wglCreateContextAttribsARB)
return NULL;
const int versionCandidates[][2] =
{
{ 4, 5 },
{ 4, 3 },
{ 3, 3 }
};
for (const auto& version : versionCandidates)
{
const int attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, version[0],
WGL_CONTEXT_MINOR_VERSION_ARB, version[1],
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
HGLRC modernContext = wglCreateContextAttribsARB(hDC, 0, attribs);
if (modernContext != NULL)
return modernContext;
}
return NULL;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg; // Windows message structure
@@ -145,6 +207,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// Window creation, setup for OpenGL context
case WM_CREATE:
{
try
{
// Store the device context
hDC = GetDC(hWnd);
@@ -155,6 +220,19 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
HGLRC modernRC = CreateModernOpenGLContext(hDC);
if (modernRC == NULL)
{
MessageBox(NULL, _T("This application requires an OpenGL 3.3+ core profile context."), _T("OpenGL initialization Error."), MB_OK);
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);
hRC = modernRC;
wglMakeCurrent(hDC, hRC);
// Initialize COM
HRESULT result;
result = CoInitialize(NULL);
@@ -180,12 +258,27 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
pOpenGLComposite = NULL;
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
catch (...)
{
ShowUnhandledExceptionMessage("Startup failed while creating the OpenGL/DeckLink runtime.");
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
}
case WM_DESTROY:
if (pOpenGLComposite)
try
{
pOpenGLComposite->Stop();
delete pOpenGLComposite;
if (pOpenGLComposite)
{
pOpenGLComposite->Stop();
delete pOpenGLComposite;
}
}
catch (...)
{
ShowUnhandledExceptionMessage("Shutdown failed while tearing down the OpenGL/DeckLink runtime.");
}
// Deselect the current rendering context and delete it
@@ -197,24 +290,46 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
break;
case WM_SIZE:
if (pOpenGLComposite)
pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
try
{
if (pOpenGLComposite)
pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
}
catch (...)
{
ShowUnhandledExceptionMessage("Resize failed inside the OpenGL runtime.");
}
break;
case WM_PAINT:
wglMakeCurrent(hDC, hRC);
try
{
wglMakeCurrent(hDC, hRC);
if (pOpenGLComposite)
pOpenGLComposite->paintGL();
if (pOpenGLComposite)
pOpenGLComposite->paintGL();
wglMakeCurrent( NULL, NULL );
wglMakeCurrent( NULL, NULL );
}
catch (...)
{
wglMakeCurrent( NULL, NULL );
ShowUnhandledExceptionMessage("Paint failed inside the OpenGL runtime.");
}
break;
case WM_KEYDOWN:
if (pOpenGLComposite && (wParam == 'R' || wParam == 'r'))
try
{
pOpenGLComposite->ReloadShader();
InvalidateRect(hWnd, NULL, FALSE);
if (pOpenGLComposite && (wParam == 'R' || wParam == 'r'))
{
pOpenGLComposite->ReloadShader();
InvalidateRect(hWnd, NULL, FALSE);
}
}
catch (...)
{
ShowUnhandledExceptionMessage("Shader reload failed inside the OpenGL runtime.");
}
break;

View File

@@ -38,12 +38,13 @@
** -LICENSE-END-
*/
#include "ControlServer.h"
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
@@ -53,12 +54,11 @@ DEFINE_GUID(IID_PinnedMemoryAllocator,
namespace
{
const char* kSlangShaderRelativePath = "apps/LoopThroughWithOpenGLCompositing/video_effect.slang";
const char* kRuntimeShaderCacheDirectory = "shader_cache";
const char* kRuntimeRawShaderFilename = "video_effect.raw.frag";
const char* kRuntimePatchedShaderFilename = "video_effect.frag";
constexpr GLuint kVideoTextureUnit = 1;
constexpr GLuint kGlobalParamsBindingPoint = 0;
const char* kDisplayModeName = "1080p59.94";
const char* kVertexShaderSource =
"#version 130\n"
"#version 430 core\n"
"out vec2 vTexCoord;\n"
"void main()\n"
"{\n"
@@ -68,34 +68,6 @@ const char* kVertexShaderSource =
" vTexCoord = texCoords[gl_VertexID];\n"
"}\n";
std::string GetExecutableDirectory()
{
char modulePath[MAX_PATH] = {};
DWORD pathLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH);
if (pathLength == 0 || pathLength == MAX_PATH)
return std::string();
std::string path(modulePath, pathLength);
std::string::size_type slashIndex = path.find_last_of("\\/");
if (slashIndex == std::string::npos)
return std::string();
return path.substr(0, slashIndex);
}
bool ReplaceAll(std::string& text, const std::string& from, const std::string& to)
{
bool replaced = false;
std::string::size_type startPos = 0;
while ((startPos = text.find(from, startPos)) != std::string::npos)
{
text.replace(startPos, from.length(), to);
startPos += to.length();
replaced = true;
}
return replaced;
}
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{
if (!errorMessage || errorMessageSize <= 0)
@@ -104,178 +76,47 @@ void CopyErrorMessage(const std::string& message, int errorMessageSize, char* er
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
}
bool LoadTextFile(const std::string& path, std::string& contents, std::string& error)
std::size_t AlignStd140(std::size_t offset, std::size_t alignment)
{
std::ifstream input(path.c_str(), std::ios::binary);
if (!input)
{
error = "Could not open fragment shader file: " + path;
return false;
}
std::ostringstream buffer;
buffer << input.rdbuf();
contents = buffer.str();
if (contents.empty())
{
error = "Fragment shader file is empty: " + path;
return false;
}
return true;
const std::size_t mask = alignment - 1;
return (offset + mask) & ~mask;
}
std::filesystem::path FindRepoRoot()
template <typename TValue>
void AppendStd140Value(std::vector<unsigned char>& buffer, std::size_t alignment, const TValue& value)
{
std::vector<std::filesystem::path> rootsToTry;
char currentDirBuffer[MAX_PATH] = {};
if (GetCurrentDirectoryA(MAX_PATH, currentDirBuffer) > 0)
rootsToTry.push_back(std::filesystem::path(currentDirBuffer));
std::string executableDirectory = GetExecutableDirectory();
if (!executableDirectory.empty())
rootsToTry.push_back(std::filesystem::path(executableDirectory));
for (const std::filesystem::path& startPath : rootsToTry)
{
std::filesystem::path candidate = startPath;
for (int depth = 0; depth < 8 && !candidate.empty(); ++depth)
{
if (std::filesystem::exists(candidate / kSlangShaderRelativePath))
return candidate;
candidate = candidate.parent_path();
}
}
return std::filesystem::path();
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));
}
bool FindSlangCompiler(const std::filesystem::path& repoRoot, std::filesystem::path& slangCompilerPath, std::string& error)
void AppendStd140Float(std::vector<unsigned char>& buffer, float value)
{
std::filesystem::path thirdPartyPath = repoRoot / "3rdParty";
if (!std::filesystem::exists(thirdPartyPath))
{
error = "Could not locate the 3rdParty directory from the application runtime path.";
return false;
}
for (const auto& entry : std::filesystem::directory_iterator(thirdPartyPath))
{
if (!entry.is_directory())
continue;
std::filesystem::path candidate = entry.path() / "bin" / "slangc.exe";
if (std::filesystem::exists(candidate))
{
slangCompilerPath = candidate;
return true;
}
}
error = "Could not find slangc.exe under 3rdParty.";
return false;
AppendStd140Value(buffer, 4, value);
}
bool RunProcessAndWait(const std::string& commandLine, std::string& error)
void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
{
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, NULL, &startupInfo, &processInfo))
{
error = "Failed to start slangc.exe.";
return false;
}
WaitForSingleObject(processInfo.hProcess, INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(processInfo.hProcess, &exitCode);
CloseHandle(processInfo.hThread);
CloseHandle(processInfo.hProcess);
if (exitCode != 0)
{
error = "slangc.exe returned a non-zero exit code while compiling the runtime shader.";
return false;
}
return true;
AppendStd140Value(buffer, 4, value);
}
bool PatchGeneratedSlangGLSL(std::string& shaderText, std::string& error)
void AppendStd140Vec2(std::vector<unsigned char>& buffer, float x, float y)
{
bool replacedVersion = ReplaceAll(shaderText, "#version 450", "#version 130");
ReplaceAll(shaderText, "#extension GL_EXT_samplerless_texture_functions : require\n", "");
ReplaceAll(shaderText, "layout(row_major) uniform;\n", "");
ReplaceAll(shaderText, "layout(row_major) buffer;\n", "");
ReplaceAll(shaderText, "layout(binding = 0)\nuniform texture2D UYVYtex_0;", "uniform sampler2D UYVYtex;");
ReplaceAll(shaderText, "layout(location = 0)\nout vec4 entryPointParam_fragmentMain_0;\n", "");
ReplaceAll(shaderText, "layout(location = 0)\nin vec2 input_texCoord_0;\n", "in vec2 vTexCoord;\n");
ReplaceAll(shaderText, "UYVYtex_0", "UYVYtex");
ReplaceAll(shaderText, "input_texCoord_0", "vTexCoord");
ReplaceAll(shaderText, "entryPointParam_fragmentMain_0 =", "gl_FragColor =");
if (!replacedVersion)
{
error = "Generated Slang GLSL did not contain the expected version header.";
return false;
}
return true;
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));
}
bool BuildFragmentShaderSourceFromSlang(std::string& shaderSource, std::string& error)
void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, float z, float w)
{
std::filesystem::path repoRoot = FindRepoRoot();
if (repoRoot.empty())
{
error = "Could not locate the repository root to load video_effect.slang.";
return false;
}
std::filesystem::path slangSourcePath = repoRoot / kSlangShaderRelativePath;
if (!std::filesystem::exists(slangSourcePath))
{
error = "Could not find video_effect.slang.";
return false;
}
std::filesystem::path slangCompilerPath;
if (!FindSlangCompiler(repoRoot, slangCompilerPath, error))
return false;
std::filesystem::path shaderCachePath = std::filesystem::path(GetExecutableDirectory()) / kRuntimeShaderCacheDirectory;
std::filesystem::create_directories(shaderCachePath);
std::filesystem::path rawShaderPath = shaderCachePath / kRuntimeRawShaderFilename;
std::filesystem::path patchedShaderPath = shaderCachePath / kRuntimePatchedShaderFilename;
std::string commandLine = "\"" + slangCompilerPath.string() + "\" \"" + slangSourcePath.string()
+ "\" -target glsl -profile glsl_430 -entry fragmentMain -stage fragment -o \"" + rawShaderPath.string() + "\"";
if (!RunProcessAndWait(commandLine, error))
return false;
if (!LoadTextFile(rawShaderPath.string(), shaderSource, error))
return false;
if (!PatchGeneratedSlangGLSL(shaderSource, error))
return false;
std::ofstream patchedShaderOutput(patchedShaderPath.string().c_str(), std::ios::binary);
if (patchedShaderOutput)
patchedShaderOutput << shaderSource;
return true;
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));
}
}
@@ -289,14 +130,16 @@ OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
mFastTransferExtensionAvailable(false),
mCaptureTexture(0),
mFBOTexture(0),
mFullscreenVAO(0),
mGlobalParamsUBO(0),
mProgram(0),
mVertexShader(0),
mFragmentShader(0),
mUYVYtexUniform(-1),
mRotateAngle(0.0f),
mRotateAngleRate(0.0f)
mGlobalParamsUBOSize(0)
{
InitializeCriticalSection(&pMutex);
mRuntimeHost = std::make_unique<RuntimeHost>();
mControlServer = std::make_unique<ControlServer>();
}
OpenGLComposite::~OpenGLComposite()
@@ -348,7 +191,26 @@ OpenGLComposite::~OpenGLComposite()
mPlayoutAllocator = NULL;
}
if (mFullscreenVAO != 0)
glDeleteVertexArrays(1, &mFullscreenVAO);
if (mGlobalParamsUBO != 0)
glDeleteBuffers(1, &mGlobalParamsUBO);
if (mIdFrameBuf != 0)
glDeleteFramebuffers(1, &mIdFrameBuf);
if (mIdColorBuf != 0)
glDeleteRenderbuffers(1, &mIdColorBuf);
if (mIdDepthBuf != 0)
glDeleteRenderbuffers(1, &mIdDepthBuf);
if (mCaptureTexture != 0)
glDeleteTextures(1, &mCaptureTexture);
if (mFBOTexture != 0)
glDeleteTextures(1, &mFBOTexture);
if (mUnpinnedTextureBuffer != 0)
glDeleteBuffers(1, &mUnpinnedTextureBuffer);
destroyShaderProgram();
if (mControlServer)
mControlServer->Stop();
DeleteCriticalSection(&pMutex);
}
@@ -564,10 +426,10 @@ void OpenGLComposite::paintGL()
// we already have the rendered frame to be played out sitting in the GPU in the mIdFrameBuf frame buffer.
// Simply copy the off-screen frame buffer to on-screen frame buffer, scaling to the viewing window size.
glBindFramebufferEXT(GL_READ_FRAMEBUFFER, mIdFrameBuf);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mIdFrameBuf);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glViewport(0, 0, mViewWidth, mViewHeight);
glBlitFramebufferEXT(0, 0, mFrameWidth, mFrameHeight, 0, 0, mViewWidth, mViewHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBlitFramebuffer(0, 0, mFrameWidth, mFrameHeight, 0, 0, mViewWidth, mViewHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
SwapBuffers(hGLDC);
ValidateRect(hGLWnd, NULL);
@@ -595,7 +457,38 @@ bool OpenGLComposite::InitOpenGLState()
if (! ResolveGLExtensions())
return false;
// Prepare the runtime shader program generated from the Slang source file.
std::string runtimeError;
if (!mRuntimeHost->Initialize(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
ControlServer::Callbacks callbacks;
callbacks.getStateJson = [this]() { return GetRuntimeStateJson(); };
callbacks.selectShader = [this](const std::string& shaderId, std::string& error) { return SelectShader(shaderId, error); };
callbacks.updateParameter = [this](const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error) {
return UpdateParameterJson(shaderId, parameterId, valueJson, error);
};
callbacks.setBypass = [this](bool bypassEnabled, std::string& error) { return SetBypassEnabled(bypassEnabled, error); };
callbacks.setMixAmount = [this](double mixAmount, std::string& error) { return SetMixAmount(mixAmount, error); };
callbacks.reloadShader = [this](std::string& error) {
if (!ReloadShader())
{
error = "Shader reload failed. See native app status for details.";
return false;
}
return true;
};
if (!mControlServer->Start(mRuntimeHost->GetUiRoot(), mRuntimeHost->GetServerPort(), callbacks, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Local control server failed to start", MB_OK);
return false;
}
mRuntimeHost->SetServerPort(mControlServer->GetPort());
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (! compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
@@ -612,54 +505,68 @@ bool OpenGLComposite::InitOpenGLState()
}
// Setup the texture which will hold the captured video frame pixels
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &mCaptureTexture);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
// Parameters to control how texels are sampled from the texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Create texture with empty data, we will update it using glTexSubImage2D each frame.
// The captured video is YCbCr 4:2:2 packed into a UYVY macropixel. OpenGL has no YCbCr format
// so treat it as RGBA 4:4:4:4 by halving the width and using GL_RGBA internal format.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mFrameWidth/2, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mFrameWidth/2, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
// Create Frame Buffer Object (FBO) to perform off-screen rendering of scene.
// This allows the render to be done on a framebuffer with width and height exactly matching the video format.
glGenFramebuffersEXT(1, &mIdFrameBuf);
glGenRenderbuffersEXT(1, &mIdColorBuf);
glGenRenderbuffersEXT(1, &mIdDepthBuf);
glGenFramebuffers(1, &mIdFrameBuf);
glGenRenderbuffers(1, &mIdColorBuf);
glGenRenderbuffers(1, &mIdDepthBuf);
glGenVertexArrays(1, &mFullscreenVAO);
glGenBuffers(1, &mGlobalParamsUBO);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mIdFrameBuf);
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
// Texture for FBO
glGenTextures(1, &mFBOTexture);
glBindTexture(GL_TEXTURE_2D, mFBOTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mFrameWidth, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mFrameWidth, mFrameHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
// Attach a depth buffer
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, mIdDepthBuf);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, mFrameWidth, mFrameHeight);
glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mFrameWidth, mFrameHeight);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, mIdDepthBuf);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
// Attach the texture which stores the playback image
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, mFBOTexture, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFBOTexture, 0);
GLenum glStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (glStatus != GL_FRAMEBUFFER_COMPLETE_EXT)
GLenum glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (glStatus != GL_FRAMEBUFFER_COMPLETE)
{
MessageBox(NULL, _T("Cannot initialize framebuffer."), _T("OpenGL initialization error."), MB_OK);
return false;
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindVertexArray(mFullscreenVAO);
glBindVertexArray(0);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
glBufferData(GL_UNIFORM_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
broadcastRuntimeState();
return true;
}
@@ -669,6 +576,9 @@ bool OpenGLComposite::InitOpenGLState()
void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bool hasNoInputSource)
{
mHasNoInputSource = hasNoInputSource;
if (mRuntimeHost)
mRuntimeHost->SetSignalStatus(!hasNoInputSource, mFrameWidth, mFrameHeight, kDisplayModeName);
if (mHasNoInputSource)
return; // don't transfer texture when there's no input
@@ -700,8 +610,6 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
}
else
{
glEnable(GL_TEXTURE_2D);
// Use a straightforward texture buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mUnpinnedTextureBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, videoPixels, GL_DYNAMIC_DRAW);
@@ -712,7 +620,6 @@ void OpenGLComposite::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFrame, bo
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glDisable(GL_TEXTURE_2D);
}
wglMakeCurrent( NULL, NULL );
@@ -738,8 +645,10 @@ void OpenGLComposite::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedFrame,
wglMakeCurrent( hGLDC, hGLRC );
// Draw the effect output to the off-screen framebuffer.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mIdFrameBuf);
glBindFramebuffer(GL_FRAMEBUFFER, mIdFrameBuf);
renderEffect();
if (mRuntimeHost)
mRuntimeHost->AdvanceFrame();
IDeckLinkVideoBuffer* outputVideoFrameBuffer;
if (outputVideoFrame->QueryInterface(IID_IDeckLinkVideoBuffer, (void**)&outputVideoFrameBuffer) != S_OK)
@@ -838,6 +747,9 @@ bool OpenGLComposite::Start()
bool OpenGLComposite::Stop()
{
if (mControlServer)
mControlServer->Stop();
mDLInput->StopStreams();
mDLInput->DisableVideoInput();
@@ -855,12 +767,24 @@ bool OpenGLComposite::ReloadShader()
wglMakeCurrent(hGLDC, hGLRC);
bool success = compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage);
if (mRuntimeHost)
mRuntimeHost->ClearReloadRequest();
wglMakeCurrent(NULL, NULL);
LeaveCriticalSection(&pMutex);
if (!success)
{
if (mRuntimeHost)
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
MessageBoxA(NULL, compilerErrorMessage, "Slang shader reload failed", MB_OK);
}
else
{
if (mRuntimeHost)
mRuntimeHost->SetCompileStatus(true, "Shader compiled successfully.");
broadcastRuntimeState();
}
return success;
}
@@ -884,12 +808,12 @@ void OpenGLComposite::destroyShaderProgram()
glDeleteShader(mVertexShader);
mVertexShader = 0;
}
mUYVYtexUniform = -1;
}
void OpenGLComposite::renderEffect()
{
PollRuntimeChanges();
glViewport(0, 0, mFrameWidth, mFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -904,26 +828,30 @@ void OpenGLComposite::renderEffect()
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0 + kVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
glBindVertexArray(mFullscreenVAO);
glUseProgram(mProgram);
if (mUYVYtexUniform >= 0)
glUniform1i(mUYVYtexUniform, 0);
if (mRuntimeHost)
{
const RuntimeRenderState state = mRuntimeHost->GetRenderState(mFrameWidth, mFrameHeight);
updateGlobalParamsBuffer(state);
}
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
if (mFastTransferExtensionAvailable)
VideoFrameTransfer::endTextureInUse(VideoFrameTransfer::CPUtoGPU);
}
// Compile a fullscreen shader pass from the runtime Slang source. The Slang compiler
// emits modern GLSL which we patch into a compatibility-profile shader that can run
// inside the sample's WGL context.
// Compile a fullscreen shader pass from the runtime Slang source into a core-profile
// GLSL program. The renderer owns the fullscreen pass and parameter UBO layout.
bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
@@ -933,8 +861,9 @@ bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMes
std::string loadError;
const char* vertexSource = kVertexShaderSource;
if (!BuildFragmentShaderSourceFromSlang(fragmentShaderSource, loadError))
if (!mRuntimeHost->BuildActiveFragmentShaderSource(fragmentShaderSource, loadError))
{
mRuntimeHost->SetCompileStatus(false, loadError);
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false;
}
@@ -983,28 +912,182 @@ bool OpenGLComposite::compileFragmentShader(int errorMessageSize, char* errorMes
mProgram = newProgram;
mVertexShader = newVertexShader;
mFragmentShader = newFragmentShader;
mUYVYtexUniform = glGetUniformLocation(mProgram, "UYVYtex");
const RuntimeRenderState state = mRuntimeHost->GetRenderState(mFrameWidth, mFrameHeight);
if (!updateGlobalParamsBuffer(state))
{
CopyErrorMessage("Failed to allocate the runtime parameter UBO.", errorMessageSize, errorMessage);
destroyShaderProgram();
return false;
}
mRuntimeHost->SetCompileStatus(true, "Shader compiled successfully.");
mRuntimeHost->ClearReloadRequest();
return true;
}
bool OpenGLComposite::PollRuntimeChanges()
{
if (!mRuntimeHost)
return true;
bool registryChanged = false;
bool reloadRequested = false;
std::string runtimeError;
if (!mRuntimeHost->PollFileChanges(registryChanged, reloadRequested, runtimeError))
{
mRuntimeHost->SetCompileStatus(false, runtimeError);
broadcastRuntimeState();
return false;
}
if (registryChanged)
broadcastRuntimeState();
if (!reloadRequested)
return true;
char compilerErrorMessage[1024] = {};
if (!compileFragmentShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
mRuntimeHost->SetCompileStatus(false, compilerErrorMessage);
mRuntimeHost->ClearReloadRequest();
broadcastRuntimeState();
return false;
}
broadcastRuntimeState();
return true;
}
void OpenGLComposite::broadcastRuntimeState()
{
if (mControlServer)
mControlServer->BroadcastState();
}
bool OpenGLComposite::updateGlobalParamsBuffer(const RuntimeRenderState& state)
{
std::vector<unsigned char> buffer;
buffer.reserve(512);
AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
AppendStd140Vec2(buffer, static_cast<float>(state.inputWidth), static_cast<float>(state.inputHeight));
AppendStd140Vec2(buffer, static_cast<float>(state.outputWidth), static_cast<float>(state.outputHeight));
AppendStd140Float(buffer, static_cast<float>(state.frameCount));
AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
AppendStd140Float(buffer, static_cast<float>(state.bypass));
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
auto valueIt = state.parameterValues.find(definition.id);
const ShaderParameterValue value = valueIt != state.parameterValues.end()
? valueIt->second
: ShaderParameterValue();
switch (definition.type)
{
case ShaderParameterType::Float:
AppendStd140Float(buffer, value.numberValues.empty() ? 0.0f : static_cast<float>(value.numberValues[0]));
break;
case ShaderParameterType::Vec2:
AppendStd140Vec2(buffer,
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 0.0f,
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 0.0f);
break;
case ShaderParameterType::Color:
AppendStd140Vec4(buffer,
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 1.0f,
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 1.0f,
value.numberValues.size() > 2 ? static_cast<float>(value.numberValues[2]) : 1.0f,
value.numberValues.size() > 3 ? static_cast<float>(value.numberValues[3]) : 1.0f);
break;
case ShaderParameterType::Boolean:
AppendStd140Int(buffer, value.booleanValue ? 1 : 0);
break;
case ShaderParameterType::Enum:
{
int selectedIndex = 0;
for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
{
if (definition.enumOptions[optionIndex].value == value.enumValue)
{
selectedIndex = static_cast<int>(optionIndex);
break;
}
}
AppendStd140Int(buffer, selectedIndex);
break;
}
}
}
buffer.resize(AlignStd140(buffer.size(), 16), 0);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
if (mGlobalParamsUBOSize != static_cast<GLsizeiptr>(buffer.size()))
{
glBufferData(GL_UNIFORM_BUFFER, static_cast<GLsizeiptr>(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
mGlobalParamsUBOSize = static_cast<GLsizeiptr>(buffer.size());
}
else
{
glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast<GLsizeiptr>(buffer.size()), buffer.data());
}
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
return true;
}
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
}
bool OpenGLComposite::SelectShader(const std::string& shaderId, std::string& error)
{
if (!mRuntimeHost->SelectShader(shaderId, error))
return false;
ReloadShader();
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::UpdateParameterJson(const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
if (!mRuntimeHost->UpdateParameter(shaderId, parameterId, parsedValue, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetBypassEnabled(bool bypassEnabled, std::string& error)
{
if (!mRuntimeHost->SetBypass(bypassEnabled, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::SetMixAmount(double mixAmount, std::string& error)
{
if (!mRuntimeHost->SetMixAmount(mixAmount, error))
return false;
broadcastRuntimeState();
return true;
}
bool OpenGLComposite::CheckOpenGLExtensions()
{
const GLubyte* strExt;
bool hasFBO;
// The GL_EXT_framebuffer_object extension is required but GL_AMD_pinned_memory is optional
strExt = glGetString (GL_EXTENSIONS);
hasFBO = strstr((char*)strExt, "GL_EXT_framebuffer_object") != NULL;
mFastTransferExtensionAvailable = VideoFrameTransfer::checkFastMemoryTransferAvailable();
if (!hasFBO)
{
MessageBox(NULL, _T("Required OpenGL extension \"GL_EXT_framebuffer_object\" is not available."), _T("OpenGL initialization error."), MB_OK);
return false;
}
if (!mFastTransferExtensionAvailable)
OutputDebugStringA("Fast memory transfer extension not available, using regular OpenGL transfer fallback instead\n");

View File

@@ -53,8 +53,10 @@
#include "DeckLinkAPI_h.h"
#include "VideoFrameTransfer.h"
#include "RuntimeHost.h"
#include <atomic>
#include <functional>
#include <map>
#include <memory>
#include <vector>
@@ -63,6 +65,7 @@
class PlayoutDelegate;
class CaptureDelegate;
class PinnedMemoryAllocator;
class ControlServer;
class OpenGLComposite
@@ -75,6 +78,11 @@ public:
bool Start();
bool Stop();
bool ReloadShader();
std::string GetRuntimeStateJson() const;
bool SelectShader(const std::string& shaderId, std::string& error);
bool UpdateParameterJson(const std::string& shaderId, const std::string& parameterId, const std::string& valueJson, std::string& error);
bool SetBypassEnabled(bool bypassEnabled, std::string& error);
bool SetMixAmount(double mixAmount, std::string& error);
void resizeGL(WORD width, WORD height);
void paintGL();
@@ -113,19 +121,24 @@ private:
GLuint mIdFrameBuf;
GLuint mIdColorBuf;
GLuint mIdDepthBuf;
GLuint mFullscreenVAO;
GLuint mGlobalParamsUBO;
GLuint mProgram;
GLuint mVertexShader;
GLuint mFragmentShader;
GLint mUYVYtexUniform;
GLfloat mRotateAngle;
GLfloat mRotateAngleRate;
GLsizeiptr mGlobalParamsUBOSize;
int mViewWidth;
int mViewHeight;
std::unique_ptr<RuntimeHost> mRuntimeHost;
std::unique_ptr<ControlServer> mControlServer;
bool InitOpenGLState();
bool compileFragmentShader(int errorMessageSize, char* errorMessage);
void destroyShaderProgram();
void renderEffect();
bool PollRuntimeChanges();
void broadcastRuntimeState();
bool updateGlobalParamsBuffer(const RuntimeRenderState& state);
};
////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
#pragma once
#include "RuntimeJson.h"
#include <chrono>
#include <filesystem>
#include <map>
#include <mutex>
#include <string>
#include <vector>
enum class ShaderParameterType
{
Float,
Vec2,
Color,
Boolean,
Enum
};
struct ShaderParameterOption
{
std::string value;
std::string label;
};
struct ShaderParameterDefinition
{
std::string id;
std::string label;
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::vector<ShaderParameterOption> enumOptions;
};
struct ShaderParameterValue
{
std::vector<double> numberValues;
bool booleanValue = false;
std::string enumValue;
};
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<ShaderParameterDefinition> parameters;
std::filesystem::file_time_type shaderWriteTime;
std::filesystem::file_time_type manifestWriteTime;
};
struct RuntimeRenderState
{
std::string activeShaderId;
std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues;
double timeSeconds = 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;
};
class RuntimeHost
{
public:
RuntimeHost();
bool Initialize(std::string& error);
bool PollFileChanges(bool& registryChanged, bool& reloadRequested, std::string& error);
bool ManualReloadRequested();
void ClearReloadRequest();
bool SelectShader(const std::string& shaderId, std::string& error);
bool UpdateParameter(const std::string& shaderId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
bool SetBypass(bool bypassEnabled, std::string& error);
bool SetMixAmount(double mixAmount, std::string& error);
void SetCompileStatus(bool succeeded, const std::string& message);
void SetSignalStatus(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
void AdvanceFrame();
bool BuildActiveFragmentShaderSource(std::string& fragmentShaderSource, std::string& error);
RuntimeRenderState GetRenderState(unsigned outputWidth, unsigned outputHeight) const;
std::string BuildStateJson() const;
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
unsigned short GetServerPort() const { return mServerPort; }
void SetServerPort(unsigned short port);
bool AutoReloadEnabled() const { return mAutoReloadEnabled; }
private:
struct AppConfig
{
std::string shaderLibrary = "shaders";
unsigned short serverPort = 8080;
bool autoReload = true;
};
struct PersistentState
{
std::string activeShaderId;
double mixAmount = 1.0;
bool bypass = false;
std::map<std::string, std::map<std::string, ShaderParameterValue>> parameterValuesByShader;
};
bool LoadConfig(std::string& error);
bool LoadPersistentState(std::string& error);
bool SavePersistentState(std::string& error) const;
bool ScanShaderPackages(std::string& error);
bool ParseShaderManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const;
bool NormalizeAndValidateValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error) const;
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition) const;
void EnsureParameterDefaultsLocked(ShaderPackage& shaderPackage);
std::string BuildWrapperSlangSource(const ShaderPackage& shaderPackage) 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;
bool ResolvePaths(std::string& error);
JsonValue BuildStateValue() const;
JsonValue SerializeParameterValue(const ShaderParameterDefinition& definition, const ShaderParameterValue& value) const;
private:
mutable std::mutex mMutex;
AppConfig mConfig;
PersistentState mPersistentState;
std::filesystem::path mRepoRoot;
std::filesystem::path mUiRoot;
std::filesystem::path mShaderRoot;
std::filesystem::path mRuntimeRoot;
std::filesystem::path mRuntimeStatePath;
std::filesystem::path mConfigPath;
std::filesystem::path mWrapperPath;
std::filesystem::path mGeneratedGlslPath;
std::filesystem::path mPatchedGlslPath;
std::map<std::string, ShaderPackage> mPackagesById;
std::vector<std::string> mPackageOrder;
std::string mActiveShaderId;
bool mReloadRequested;
bool mCompileSucceeded;
std::string mCompileMessage;
bool mHasSignal;
unsigned mSignalWidth;
unsigned mSignalHeight;
std::string mSignalModeName;
unsigned short mServerPort;
bool mAutoReloadEnabled;
double mMixAmount;
bool mBypass;
std::chrono::steady_clock::time_point mStartTime;
std::chrono::steady_clock::time_point mLastScanTime;
uint64_t mFrameCounter;
};

View File

@@ -0,0 +1,500 @@
#include "stdafx.h"
#include "RuntimeJson.h"
#include <cctype>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <limits>
#include <sstream>
namespace
{
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':
setError("Unicode escape sequences are not supported in this JSON parser.");
return false;
default:
setError("Invalid escape sequence in JSON string.");
return false;
}
}
else
{
result << ch;
}
}
setError("Unexpected end of JSON string.");
return false;
}
bool parseNumber(JsonValue& value)
{
std::size_t start = mPosition;
if (mText[mPosition] == '-')
++mPosition;
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
++mPosition;
if (mPosition < mText.size() && mText[mPosition] == '.')
{
++mPosition;
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;
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
++mPosition;
}
std::string token = mText.substr(start, mPosition - start);
char* endPtr = nullptr;
double parsed = strtod(token.c_str(), &endPtr);
if (endPtr == token.c_str() || *endPtr != '\0')
{
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: 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.mType = Type::Array;
return value;
}
JsonValue JsonValue::MakeObject()
{
JsonValue value;
value.mType = 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)
{
mType = Type::Array;
mArrayValue.clear();
}
return mArrayValue;
}
std::map<std::string, JsonValue>& JsonValue::object()
{
if (mType != Type::Object)
{
mType = Type::Object;
mObjectValue.clear();
}
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;
}
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();
}

View File

@@ -0,0 +1,62 @@
#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:
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);

View File

@@ -72,6 +72,9 @@ bool VideoFrameTransfer::isNvidiaDvpAvailable()
{
// Look for supported graphics boards
const GLubyte* renderer = glGetString(GL_RENDERER);
if (renderer == NULL)
return false;
bool hasDvp = (strstr((char*)renderer, "Quadro") != NULL);
return hasDvp;
}
@@ -80,6 +83,13 @@ bool VideoFrameTransfer::isAMDPinnedMemoryAvailable()
{
// GL_AMD_pinned_memory presence indicates GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD buffer target is supported
const GLubyte* strExt = glGetString(GL_EXTENSIONS);
if (strExt == NULL)
{
// In a core profile context GL_EXTENSIONS is no longer queryable via glGetString().
// Treat this as "extension unavailable" for now; the fast-transfer path is optional.
return false;
}
bool hasAMDPinned = (strstr((char*)strExt, "GL_AMD_pinned_memory") != NULL);
return hasAMDPinned;
}

View File

@@ -47,8 +47,14 @@
#include "targetver.h"
#ifndef NOMINMAX
#define NOMINMAX
#endif
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
// C RunTime Header Files

View File

@@ -1,79 +0,0 @@
// Source-of-truth shader in Slang.
// The current OpenGL sample still runs the checked-in GLSL fallback because it uses
// legacy fixed-function texture coordinates in its fragment stage.
struct FragmentInput
{
float4 position : SV_Position;
float2 texCoord : TEXCOORD0;
};
Texture2D<float4> UYVYtex;
float4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)
{
Y = (Y * 256.0 - 16.0) / 219.0;
Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;
Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;
float r = Y + 1.5748 * Cr;
float g = Y - 0.1873 * Cb - 0.4681 * Cr;
float b = Y + 1.8556 * Cb;
return float4(r, g, b, a);
}
float4 bilinear(float4 W, float4 X, float4 Y, float4 Z, float2 weight)
{
float4 m0 = lerp(W, Z, weight.x);
float4 m1 = lerp(X, Y, weight.x);
return lerp(m0, m1, weight.y);
}
void textureGatherYUV(Texture2D<float4> textureSampler, float2 tc, out float4 W, out float4 X, out float4 Y, out float4 Z)
{
uint width = 0;
uint height = 0;
textureSampler.GetDimensions(width, height);
int2 tx = int2(tc * float2(width, height));
int2 tmin = int2(0, 0);
int2 tmax = int2(int(width), int(height)) - int2(1, 1);
W = textureSampler.Load(int3(tx, 0));
X = textureSampler.Load(int3(clamp(tx + int2(0, 1), tmin, tmax), 0));
Y = textureSampler.Load(int3(clamp(tx + int2(1, 1), tmin, tmax), 0));
Z = textureSampler.Load(int3(clamp(tx + int2(1, 0), tmin, tmax), 0));
}
[shader("fragment")]
float4 fragmentMain(FragmentInput input) : SV_Target
{
float2 tc = input.texCoord;
float alpha = 0.7;
float4 macro, macroU, macroR, macroUR;
float4 pixel, pixelR, pixelU, pixelUR;
textureGatherYUV(UYVYtex, tc, macro, macroU, macroUR, macroR);
uint width = 0;
uint height = 0;
UYVYtex.GetDimensions(width, height);
float2 off = frac(tc * float2(width, height));
if (off.x > 0.5)
{
pixel = rec709YCbCr2rgba(macro.a, macro.b, macro.r, alpha);
pixelR = rec709YCbCr2rgba(macroR.g, macroR.b, macroR.r, alpha);
pixelU = rec709YCbCr2rgba(macroU.a, macroU.b, macroU.r, alpha);
pixelUR = rec709YCbCr2rgba(macroUR.g, macroUR.b, macroUR.r, alpha);
}
else
{
pixel = rec709YCbCr2rgba(macro.g, macro.b, macro.r, alpha);
pixelR = rec709YCbCr2rgba(macro.a, macro.b, macro.r, alpha);
pixelU = rec709YCbCr2rgba(macroU.g, macroU.b, macroU.r, alpha);
pixelUR = rec709YCbCr2rgba(macroU.a, macroU.b, macroU.r, alpha);
}
return bilinear(pixel, pixelU, pixelUR, pixelR, off);
}