Working
This commit is contained in:
454
apps/LoopThroughWithOpenGLCompositing/ControlServer.cpp
Normal file
454
apps/LoopThroughWithOpenGLCompositing/ControlServer.cpp
Normal 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);
|
||||
}
|
||||
68
apps/LoopThroughWithOpenGLCompositing/ControlServer.h
Normal file
68
apps/LoopThroughWithOpenGLCompositing/ControlServer.h
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////
|
||||
|
||||
1284
apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp
Normal file
1284
apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp
Normal file
File diff suppressed because it is too large
Load Diff
173
apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h
Normal file
173
apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h
Normal 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;
|
||||
};
|
||||
500
apps/LoopThroughWithOpenGLCompositing/RuntimeJson.cpp
Normal file
500
apps/LoopThroughWithOpenGLCompositing/RuntimeJson.cpp
Normal 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();
|
||||
}
|
||||
62
apps/LoopThroughWithOpenGLCompositing/RuntimeJson.h
Normal file
62
apps/LoopThroughWithOpenGLCompositing/RuntimeJson.h
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user