Clean up
Some checks failed
CI / Native Windows Build And Tests (push) Failing after 16s
CI / React UI Build (push) Successful in 38s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-18 14:19:29 +10:00
parent 3ffb562ff7
commit f461a05c65
222 changed files with 0 additions and 45423 deletions

View File

@@ -1,668 +0,0 @@
#include "RenderEngine.h"
#include <gl/gl.h>
#include <algorithm>
#include <cstring>
#include <sstream>
RenderEngine::RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
ScreenshotCallback screenshotReady,
PreviewPaintCallback previewPaint) :
mRenderer(),
mRenderPass(mRenderer),
mRenderPipeline(mRenderer, runtimeSnapshotProvider, healthTelemetry, std::move(renderEffect), std::move(screenshotReady), std::move(previewPaint)),
mShaderPrograms(mRenderer, runtimeSnapshotProvider),
mHealthTelemetry(healthTelemetry),
mHdc(hdc),
mHglrc(hglrc),
mFrameStateResolver(runtimeSnapshotProvider)
{
}
RenderEngine::~RenderEngine()
{
StopRenderThread();
if (!mResourcesDestroyed)
{
mRenderer.DestroyResources();
mResourcesDestroyed = true;
}
}
bool RenderEngine::StartRenderThread()
{
if (mRenderThreadRunning)
return true;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mRenderThreadStopping = false;
}
std::promise<bool> ready;
std::future<bool> readyResult = ready.get_future();
mRenderThread = std::thread(&RenderEngine::RenderThreadMain, this, std::move(ready));
if (!readyResult.get())
{
if (mRenderThread.joinable())
mRenderThread.join();
return false;
}
return true;
}
void RenderEngine::StopRenderThread()
{
if (mRenderThreadRunning)
{
InvokeOnRenderThread([this]() {
if (!mResourcesDestroyed)
{
mRenderer.DestroyResources();
mResourcesDestroyed = true;
}
});
}
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mRenderThreadStopping = true;
}
mRenderThreadCondition.notify_one();
if (mRenderThread.joinable())
mRenderThread.join();
}
void RenderEngine::RenderThreadMain(std::promise<bool> ready)
{
mRenderThreadId = GetCurrentThreadId();
if (!wglMakeCurrent(mHdc, mHglrc))
{
mRenderThreadId = 0;
ready.set_value(false);
return;
}
mRenderThreadRunning = true;
ready.set_value(true);
for (;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mRenderThreadMutex);
mRenderThreadCondition.wait(lock, [this]() {
return mRenderThreadStopping || !mRenderThreadTasks.empty();
});
if (mRenderThreadStopping && mRenderThreadTasks.empty())
break;
task = std::move(mRenderThreadTasks.front());
mRenderThreadTasks.pop();
}
try
{
task();
}
catch (...)
{
OutputDebugStringA("Render thread task failed with an unhandled exception.\n");
}
}
wglMakeCurrent(NULL, NULL);
mRenderThreadRunning = false;
mRenderThreadId = 0;
}
void RenderEngine::ReportRenderThreadRequestFailure(const char* operationName, const char* reason)
{
std::ostringstream message;
message << "Render thread request failed";
if (operationName && operationName[0] != '\0')
message << " [" << operationName << "]";
if (reason && reason[0] != '\0')
message << ": " << reason;
message << ".\n";
OutputDebugStringA(message.str().c_str());
}
bool RenderEngine::IsRenderThreadAccessExpected() const
{
return !mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId;
}
void RenderEngine::ReportWrongThreadRenderAccess(const char* operationName) const
{
if (IsRenderThreadAccessExpected())
return;
std::ostringstream message;
message << "Wrong-thread render access detected";
if (operationName && operationName[0] != '\0')
message << " [" << operationName << "]";
message << ".\n";
OutputDebugStringA(message.str().c_str());
}
bool RenderEngine::CompileDecodeShader(int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
return mShaderPrograms.CompileDecodeShader(errorMessageSize, errorMessage);
});
}
bool RenderEngine::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, errorMessageSize, errorMessage]() {
return mShaderPrograms.CompileOutputPackShader(errorMessageSize, errorMessage);
});
}
bool RenderEngine::InitializeResources(
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
unsigned outputFrameWidth,
unsigned outputFrameHeight,
unsigned outputPackTextureWidth,
std::string& error)
{
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, captureTextureWidth, outputFrameWidth, outputFrameHeight, outputPackTextureWidth, &error]() {
return mRenderer.InitializeResources(
inputFrameWidth,
inputFrameHeight,
captureTextureWidth,
outputFrameWidth,
outputFrameHeight,
outputPackTextureWidth,
error);
});
}
bool RenderEngine::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
return mShaderPrograms.CompileLayerPrograms(inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
});
}
bool RenderEngine::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
return InvokeOnRenderThread([this, &preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage]() {
return mShaderPrograms.CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage);
});
}
bool RenderEngine::ApplyPreparedShaderBuild(
const PreparedShaderBuild& preparedBuild,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState,
int errorMessageSize,
char* errorMessage)
{
if (!CommitPreparedLayerPrograms(preparedBuild, inputFrameWidth, inputFrameHeight, errorMessageSize, errorMessage))
return false;
mFrameStateResolver.StoreCommittedSnapshot(preparedBuild.renderSnapshot, mShaderPrograms.CommittedLayerStates());
ResetTemporalHistoryState();
if (!preserveFeedbackState)
ResetShaderFeedbackState();
return true;
}
void RenderEngine::ResetTemporalHistoryState()
{
InvokeOnRenderThread([this]() {
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
ProcessRenderResetCommandsOnRenderThread();
});
}
void RenderEngine::ResetShaderFeedbackState()
{
InvokeOnRenderThread([this]() {
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::ShaderFeedbackOnly);
ProcessRenderResetCommandsOnRenderThread();
});
}
void RenderEngine::ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope)
{
InvokeOnRenderThread([this, resetScope]() {
switch (resetScope)
{
case RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly:
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryOnly);
ProcessRenderResetCommandsOnRenderThread();
break;
case RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback:
mRenderCommandQueue.RequestRenderReset(RenderCommandResetScope::TemporalHistoryAndFeedback);
ProcessRenderResetCommandsOnRenderThread();
break;
case RuntimeCoordinatorRenderResetScope::None:
default:
break;
}
});
}
void RenderEngine::ResetTemporalHistoryStateOnRenderThread()
{
ReportWrongThreadRenderAccess("reset-temporal-history");
mShaderPrograms.ResetTemporalHistoryState();
}
void RenderEngine::ResetShaderFeedbackStateOnRenderThread()
{
ReportWrongThreadRenderAccess("reset-shader-feedback");
mShaderPrograms.ResetShaderFeedbackState();
}
void RenderEngine::ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope)
{
switch (resetScope)
{
case RenderCommandResetScope::ShaderFeedbackOnly:
ResetShaderFeedbackStateOnRenderThread();
break;
case RenderCommandResetScope::TemporalHistoryOnly:
ResetTemporalHistoryStateOnRenderThread();
break;
case RenderCommandResetScope::TemporalHistoryAndFeedback:
ResetTemporalHistoryStateOnRenderThread();
ResetShaderFeedbackStateOnRenderThread();
break;
case RenderCommandResetScope::None:
default:
break;
}
}
void RenderEngine::ProcessRenderResetCommandsOnRenderThread()
{
RenderCommandResetScope resetScope = RenderCommandResetScope::None;
while (mRenderCommandQueue.TryTakeRenderReset(resetScope))
ApplyRenderResetOnRenderThread(resetScope);
}
void RenderEngine::EnqueuePreviewPresentWake()
{
if (!mRenderThreadRunning)
return;
bool shouldNotify = false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (!mRenderThreadStopping && !mPreviewPresentWakePending)
{
mPreviewPresentWakePending = true;
mRenderThreadTasks.push([this]() {
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mPreviewPresentWakePending = false;
}
ProcessPreviewPresentCommandsOnRenderThread();
});
shouldNotify = true;
}
}
if (shouldNotify)
mRenderThreadCondition.notify_one();
}
void RenderEngine::ProcessPreviewPresentCommandsOnRenderThread()
{
RenderPreviewPresentRequest request;
if (mRenderCommandQueue.TryTakePreviewPresent(request))
PresentPreviewOnRenderThread(request.outputFrameWidth, request.outputFrameHeight);
}
void RenderEngine::EnqueueInputUploadWake()
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return;
bool shouldNotify = false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (!mRenderThreadStopping && !mInputUploadWakePending)
{
mInputUploadWakePending = true;
mRenderThreadTasks.push([this]() {
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mInputUploadWakePending = false;
}
ProcessInputUploadCommandsOnRenderThread();
});
shouldNotify = true;
}
}
if (shouldNotify)
mRenderThreadCondition.notify_one();
}
void RenderEngine::ProcessInputUploadCommandsOnRenderThread()
{
RenderInputUploadRequest request;
while (mRenderCommandQueue.TryTakeInputUpload(request))
{
if (request.ownedBytes.empty())
continue;
request.inputFrame.bytes = request.ownedBytes.data();
UploadInputFrameOnRenderThread(request.inputFrame, request.videoState);
}
}
void RenderEngine::EnqueueScreenshotCaptureWake()
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return;
bool shouldNotify = false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (!mRenderThreadStopping && !mScreenshotCaptureWakePending)
{
mScreenshotCaptureWakePending = true;
mRenderThreadTasks.push([this]() {
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mScreenshotCaptureWakePending = false;
}
ProcessScreenshotCaptureCommandsOnRenderThread();
});
shouldNotify = true;
}
}
if (shouldNotify)
mRenderThreadCondition.notify_one();
}
void RenderEngine::ProcessScreenshotCaptureCommandsOnRenderThread()
{
RenderScreenshotCaptureRequest request;
ScreenshotCaptureCallback completion;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
completion = mScreenshotCaptureCompletion;
}
while (mRenderCommandQueue.TryTakeScreenshotCapture(request))
{
if (!completion)
continue;
std::vector<unsigned char> topDownPixels;
if (CaptureOutputFrameRgbaTopDownOnRenderThread(request.width, request.height, topDownPixels))
completion(request.width, request.height, std::move(topDownPixels));
}
}
void RenderEngine::ClearOscOverlayState()
{
InvokeOnRenderThread([this]() {
mRuntimeLiveState.Clear();
});
}
void RenderEngine::ClearOscOverlayStateForLayerKey(const std::string& layerKey)
{
InvokeOnRenderThread([this, layerKey]() {
mRuntimeLiveState.ClearForLayerKey(layerKey);
});
}
void RenderEngine::UpdateOscOverlayState(
const std::vector<OscOverlayUpdate>& updates,
const std::vector<OscOverlayCommitCompletion>& completedCommits)
{
std::vector<RuntimeLiveOscCommitCompletion> liveCompletions;
liveCompletions.reserve(completedCommits.size());
for (const OscOverlayCommitCompletion& completedCommit : completedCommits)
liveCompletions.push_back({ completedCommit.routeKey, completedCommit.generation });
mRuntimeLiveState.ApplyOscCommitCompletions(liveCompletions);
std::vector<RuntimeLiveOscUpdate> liveUpdates;
liveUpdates.reserve(updates.size());
for (const OscOverlayUpdate& update : updates)
liveUpdates.push_back({ update.routeKey, update.layerKey, update.parameterKey, update.targetValue });
mRuntimeLiveState.ApplyOscUpdates(liveUpdates);
}
void RenderEngine::ResizeView(int width, int height)
{
InvokeOnRenderThread([this, width, height]() {
mRenderer.ResizeView(width, height);
});
}
bool RenderEngine::TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight)
{
if (!force)
{
if (previewFps == 0)
return false;
const auto now = std::chrono::steady_clock::now();
const auto minimumInterval = std::chrono::microseconds(1000000 / (previewFps == 0 ? 1u : previewFps));
if (mLastPreviewPresentTime != std::chrono::steady_clock::time_point() &&
now - mLastPreviewPresentTime < minimumInterval)
{
return false;
}
}
if (mRenderThreadRunning)
{
mRenderCommandQueue.RequestPreviewPresent({ outputFrameWidth, outputFrameHeight });
EnqueuePreviewPresentWake();
return true;
}
ReportRenderThreadRequestFailure("preview-present", "render thread is not running");
return false;
}
bool RenderEngine::PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight)
{
ReportWrongThreadRenderAccess("preview-present");
mRenderer.PresentToWindow(mHdc, outputFrameWidth, outputFrameHeight);
mLastPreviewPresentTime = std::chrono::steady_clock::now();
return true;
}
bool RenderEngine::RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion)
{
if (width == 0 || height == 0 || !completion)
return false;
if (!mRenderThreadRunning)
return false;
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mScreenshotCaptureCompletion = std::move(completion);
}
mRenderCommandQueue.RequestScreenshotCapture({ width, height });
EnqueueScreenshotCaptureWake();
return true;
}
bool RenderEngine::QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
{
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
return true;
if (inputFrame.rowBytes <= 0 || inputFrame.height == 0)
return false;
const std::size_t byteCount = static_cast<std::size_t>(inputFrame.rowBytes) * inputFrame.height;
RenderInputUploadRequest request;
request.inputFrame = inputFrame;
request.videoState = videoState;
request.ownedBytes.resize(byteCount);
std::memcpy(request.ownedBytes.data(), inputFrame.bytes, byteCount);
request.inputFrame.bytes = nullptr;
mRenderCommandQueue.RequestInputUpload(request);
EnqueueInputUploadWake();
return true;
}
bool RenderEngine::UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState)
{
ReportWrongThreadRenderAccess("input-upload");
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.TextureUploadBuffer());
glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, inputFrame.bytes, GL_DYNAMIC_DRAW);
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
else
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, videoState.captureTextureWidth, videoState.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
return true;
}
bool RenderEngine::RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
if (mRenderThreadRunning)
{
const auto queuedAt = std::chrono::steady_clock::now();
return TryInvokeOnRenderThread("output-render", [this, &context, &outputFrame, queuedAt]() {
const auto startedAt = std::chrono::steady_clock::now();
const double queueWaitMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(startedAt - queuedAt).count();
mHealthTelemetry.TryRecordOutputRenderQueueWait(queueWaitMilliseconds);
mRenderCommandQueue.RequestOutputFrame({ context.videoState, context.completion });
RenderOutputFrameRequest request;
return mRenderCommandQueue.TryTakeOutputFrame(request) &&
RenderOutputFrameOnRenderThread({ request.videoState, request.completion }, outputFrame);
});
}
ReportRenderThreadRequestFailure("output-render", "render thread is not running");
return false;
}
bool RenderEngine::RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
ReportWrongThreadRenderAccess("output-render");
ProcessRenderResetCommandsOnRenderThread();
ProcessInputUploadCommandsOnRenderThread();
return mRenderPipeline.RenderFrame(context, outputFrame);
}
bool RenderEngine::ResolveRenderFrameState(
const RenderFrameInput& input,
std::vector<OscOverlayCommitRequest>* commitRequests,
RenderFrameState& frameState)
{
std::vector<RuntimeLiveOscCommitRequest> liveCommitRequests;
const bool resolved = mFrameStateResolver.Resolve(
input,
mShaderPrograms.CommittedLayerStates(),
mRuntimeLiveState,
commitRequests ? &liveCommitRequests : nullptr,
frameState);
if (commitRequests)
{
for (const RuntimeLiveOscCommitRequest& request : liveCommitRequests)
commitRequests->push_back({ request.routeKey, request.layerKey, request.parameterKey, request.value, request.generation });
}
return resolved;
}
void RenderEngine::RenderPreparedFrame(const RenderFrameState& frameState)
{
RenderLayerStack(
frameState.hasInputSource,
frameState.layerStates,
frameState.inputFrameWidth,
frameState.inputFrameHeight,
frameState.captureTextureWidth,
frameState.inputPixelFormat,
frameState.historyCap);
}
void RenderEngine::RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap)
{
ReportWrongThreadRenderAccess("render-layer-stack");
mRenderPass.Render(
hasInputSource,
layerStates,
inputFrameWidth,
inputFrameHeight,
captureTextureWidth,
inputPixelFormat,
historyCap,
[this](const RuntimeRenderState& state, OpenGLRenderer::LayerProgram::TextBinding& textBinding, std::string& error) {
return mShaderPrograms.UpdateTextBindingTexture(state, textBinding, error);
},
[this](const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable) {
return mShaderPrograms.UpdateGlobalParamsBuffer(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
});
}
bool RenderEngine::ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels)
{
ReportWrongThreadRenderAccess("read-output-frame-rgba");
if (width == 0 || height == 0)
return false;
bottomUpPixels.resize(static_cast<std::size_t>(width) * height * 4);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, bottomUpPixels.data());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
return true;
}
bool RenderEngine::CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels)
{
std::vector<unsigned char> bottomUpPixels;
if (!ReadOutputFrameRgbaOnRenderThread(width, height, bottomUpPixels))
return false;
topDownPixels.resize(bottomUpPixels.size());
const std::size_t rowBytes = static_cast<std::size_t>(width) * 4;
for (unsigned y = 0; y < height; ++y)
{
const unsigned sourceY = height - 1 - y;
std::copy(
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>(sourceY * rowBytes),
bottomUpPixels.begin() + static_cast<std::ptrdiff_t>((sourceY + 1) * rowBytes),
topDownPixels.begin() + static_cast<std::ptrdiff_t>(y * rowBytes));
}
return true;
}

View File

@@ -1,232 +0,0 @@
#pragma once
#include "OpenGLRenderPass.h"
#include "OpenGLRenderPipeline.h"
#include "OpenGLRenderer.h"
#include "OpenGLShaderPrograms.h"
#include "RenderCommandQueue.h"
#include "RenderFrameState.h"
#include "RenderFrameStateResolver.h"
#include "HealthTelemetry.h"
#include "RuntimeCoordinator.h"
#include "RuntimeSnapshotProvider.h"
#include <windows.h>
#include <atomic>
#include <cstdint>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <utility>
#include <vector>
class RenderEngine
{
public:
using RenderEffectCallback = std::function<void()>;
using ScreenshotCallback = std::function<void()>;
using ScreenshotCaptureCallback = std::function<void(unsigned, unsigned, std::vector<unsigned char>)>;
using PreviewPaintCallback = std::function<void()>;
struct OscOverlayUpdate
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue targetValue;
};
struct OscOverlayCommitCompletion
{
std::string routeKey;
uint64_t generation = 0;
};
struct OscOverlayCommitRequest
{
std::string routeKey;
std::string layerKey;
std::string parameterKey;
JsonValue value;
uint64_t generation = 0;
};
RenderEngine(
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
HDC hdc,
HGLRC hglrc,
RenderEffectCallback renderEffect,
ScreenshotCallback screenshotReady,
PreviewPaintCallback previewPaint);
~RenderEngine();
bool StartRenderThread();
void StopRenderThread();
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
bool InitializeResources(
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
unsigned outputFrameWidth,
unsigned outputFrameHeight,
unsigned outputPackTextureWidth,
std::string& error);
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool ApplyPreparedShaderBuild(
const PreparedShaderBuild& preparedBuild,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
bool preserveFeedbackState,
int errorMessageSize,
char* errorMessage);
void ResetTemporalHistoryState();
void ResetShaderFeedbackState();
void ApplyRuntimeCoordinatorRenderReset(RuntimeCoordinatorRenderResetScope resetScope);
void ClearOscOverlayState();
void ClearOscOverlayStateForLayerKey(const std::string& layerKey);
void UpdateOscOverlayState(
const std::vector<OscOverlayUpdate>& updates,
const std::vector<OscOverlayCommitCompletion>& completedCommits);
void ResizeView(int width, int height);
bool TryPresentPreview(bool force, unsigned previewFps, unsigned outputFrameWidth, unsigned outputFrameHeight);
bool RequestScreenshotCapture(unsigned width, unsigned height, ScreenshotCaptureCallback completion);
bool QueueInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
bool RequestOutputFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
bool ResolveRenderFrameState(
const RenderFrameInput& input,
std::vector<OscOverlayCommitRequest>* commitRequests,
RenderFrameState& frameState);
void RenderPreparedFrame(const RenderFrameState& frameState);
private:
static constexpr std::chrono::milliseconds kRenderThreadRequestTimeout{ 250 };
struct RenderThreadTaskState
{
std::atomic<bool> started = false;
std::atomic<bool> cancelled = false;
};
template<typename Func>
auto InvokeOnRenderThread(Func&& func) -> decltype(func())
{
using Result = decltype(func());
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return func();
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Func>(func));
std::future<Result> result = task->get_future();
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
mRenderThreadTasks.push([task]() { (*task)(); });
}
mRenderThreadCondition.notify_one();
return result.get();
}
template<typename Func>
bool TryInvokeOnRenderThread(const char* operationName, Func&& func)
{
if (!mRenderThreadRunning || GetCurrentThreadId() == mRenderThreadId)
return func();
auto state = std::make_shared<RenderThreadTaskState>();
auto task = std::make_shared<std::packaged_task<bool()>>(
[state, func = std::forward<Func>(func)]() mutable {
state->started = true;
if (state->cancelled)
return false;
return func();
});
std::future<bool> result = task->get_future();
{
std::lock_guard<std::mutex> lock(mRenderThreadMutex);
if (mRenderThreadStopping)
{
ReportRenderThreadRequestFailure(operationName, "render thread is stopping");
return false;
}
mRenderThreadTasks.push([task]() { (*task)(); });
}
mRenderThreadCondition.notify_one();
if (result.wait_for(kRenderThreadRequestTimeout) == std::future_status::ready)
return result.get();
if (!state->started)
{
state->cancelled = true;
ReportRenderThreadRequestFailure(operationName, "timed out before execution");
return false;
}
ReportRenderThreadRequestFailure(operationName, "exceeded timeout while executing; waiting for safe completion");
return result.get();
}
void RenderThreadMain(std::promise<bool> ready);
void ReportRenderThreadRequestFailure(const char* operationName, const char* reason);
bool IsRenderThreadAccessExpected() const;
void ReportWrongThreadRenderAccess(const char* operationName) const;
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
void RenderLayerStack(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap);
void ResetTemporalHistoryStateOnRenderThread();
void ResetShaderFeedbackStateOnRenderThread();
void ApplyRenderResetOnRenderThread(RenderCommandResetScope resetScope);
void ProcessRenderResetCommandsOnRenderThread();
void EnqueuePreviewPresentWake();
void ProcessPreviewPresentCommandsOnRenderThread();
void EnqueueInputUploadWake();
void ProcessInputUploadCommandsOnRenderThread();
void EnqueueScreenshotCaptureWake();
void ProcessScreenshotCaptureCommandsOnRenderThread();
bool PresentPreviewOnRenderThread(unsigned outputFrameWidth, unsigned outputFrameHeight);
bool UploadInputFrameOnRenderThread(const VideoIOFrame& inputFrame, const VideoIOState& videoState);
bool RenderOutputFrameOnRenderThread(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
bool ReadOutputFrameRgbaOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& bottomUpPixels);
bool CaptureOutputFrameRgbaTopDownOnRenderThread(unsigned width, unsigned height, std::vector<unsigned char>& topDownPixels);
OpenGLRenderer mRenderer;
OpenGLRenderPass mRenderPass;
OpenGLRenderPipeline mRenderPipeline;
OpenGLShaderPrograms mShaderPrograms;
HealthTelemetry& mHealthTelemetry;
HDC mHdc;
HGLRC mHglrc;
std::chrono::steady_clock::time_point mLastPreviewPresentTime;
RenderCommandQueue mRenderCommandQueue;
RenderFrameStateResolver mFrameStateResolver;
RuntimeLiveState mRuntimeLiveState;
std::thread mRenderThread;
std::atomic<DWORD> mRenderThreadId = 0;
std::mutex mRenderThreadMutex;
std::condition_variable mRenderThreadCondition;
std::queue<std::function<void()>> mRenderThreadTasks;
std::atomic<bool> mRenderThreadRunning = false;
bool mRenderThreadStopping = false;
bool mPreviewPresentWakePending = false;
bool mInputUploadWakePending = false;
bool mScreenshotCaptureWakePending = false;
ScreenshotCaptureCallback mScreenshotCaptureCompletion;
bool mResourcesDestroyed = false;
};

View File

@@ -1,428 +0,0 @@
#include "DeckLinkDisplayMode.h"
#include "OpenGLComposite.h"
#include "GLExtensions.h"
#include "PngScreenshotWriter.h"
#include "RenderEngine.h"
#include "RuntimeCoordinator.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeServiceLiveBridge.h"
#include "RuntimeServices.h"
#include "RuntimeSnapshotProvider.h"
#include "RuntimeStore.h"
#include "RuntimeUpdateController.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <chrono>
#include <ctime>
#include <filesystem>
#include <iomanip>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
OpenGLComposite::OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC) :
hGLWnd(hWnd), hGLDC(hDC), hGLRC(hRC)
{
mRuntimeStore = std::make_unique<RuntimeStore>();
mRuntimeEventDispatcher = std::make_unique<RuntimeEventDispatcher>();
mRuntimeSnapshotProvider = std::make_unique<RuntimeSnapshotProvider>(mRuntimeStore->GetRenderSnapshotBuilder(), *mRuntimeEventDispatcher);
mRuntimeCoordinator = std::make_unique<RuntimeCoordinator>(*mRuntimeStore, *mRuntimeEventDispatcher);
mRenderEngine = std::make_unique<RenderEngine>(
*mRuntimeSnapshotProvider,
mRuntimeStore->GetHealthTelemetry(),
hGLDC,
hGLRC,
[this]() { renderEffect(); },
[]() {},
[this]() { paintGL(false); });
mVideoBackend = std::make_unique<VideoBackend>(*mRenderEngine, mRuntimeStore->GetHealthTelemetry(), *mRuntimeEventDispatcher);
mShaderBuildQueue = std::make_unique<ShaderBuildQueue>(*mRuntimeSnapshotProvider, *mRuntimeEventDispatcher);
mRuntimeServices = std::make_unique<RuntimeServices>(*mRuntimeEventDispatcher);
mRuntimeUpdateController = std::make_unique<RuntimeUpdateController>(
*mRuntimeStore,
*mRuntimeCoordinator,
*mRuntimeEventDispatcher,
*mRuntimeServices,
*mRenderEngine,
*mShaderBuildQueue,
*mVideoBackend);
}
OpenGLComposite::~OpenGLComposite()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
if (mShaderBuildQueue)
mShaderBuildQueue->Stop();
if (mVideoBackend)
mVideoBackend->ReleaseResources();
if (mRuntimeStore)
{
std::string persistenceError;
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
}
}
bool OpenGLComposite::InitDeckLink()
{
return InitVideoIO();
}
bool OpenGLComposite::InitVideoIO()
{
VideoFormatSelection videoModes;
std::string initFailureReason;
if (mRuntimeStore && mRuntimeStore->GetRuntimeRepositoryRoot().empty())
{
std::string runtimeError;
if (!mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
}
if (mRuntimeStore)
{
if (!ResolveConfiguredVideoFormats(
mRuntimeStore->GetConfiguredInputVideoFormat(),
mRuntimeStore->GetConfiguredInputFrameRate(),
mRuntimeStore->GetConfiguredOutputVideoFormat(),
mRuntimeStore->GetConfiguredOutputFrameRate(),
videoModes,
initFailureReason))
{
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink mode configuration error", MB_OK);
return false;
}
}
if (!mVideoBackend->DiscoverDevicesAndModes(videoModes, initFailureReason))
{
const char* title = initFailureReason == "Please install the Blackmagic DeckLink drivers to use the features of this application."
? "This application requires the DeckLink drivers installed."
: "DeckLink initialization failed";
MessageBoxA(NULL, initFailureReason.c_str(), title, MB_OK | MB_ICONERROR);
return false;
}
const bool outputAlphaRequired = mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured();
if (!mVideoBackend->SelectPreferredFormats(videoModes, outputAlphaRequired, initFailureReason))
goto error;
if (! CheckOpenGLExtensions())
{
initFailureReason = "OpenGL extension checks failed.";
goto error;
}
if (! InitOpenGLState())
{
initFailureReason = "OpenGL state initialization failed.";
goto error;
}
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->OutputModelName().empty()
? "DeckLink output device selected."
: ("Selected output device: " + mVideoBackend->OutputModelName()));
// Resize window to match output video frame, but scale large formats down by half for viewing.
if (mVideoBackend->OutputFrameWidth() < 1920)
resizeWindow(mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight());
else
resizeWindow(mVideoBackend->OutputFrameWidth() / 2, mVideoBackend->OutputFrameHeight() / 2);
if (!mVideoBackend->ConfigureInput(videoModes.input, initFailureReason))
{
goto error;
}
if (!mVideoBackend->HasInputDevice())
mVideoBackend->ReportNoInputDeviceSignalStatus();
if (!mVideoBackend->ConfigureOutput(videoModes.output, mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(), initFailureReason))
{
goto error;
}
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
mVideoBackend->StatusMessage());
return true;
error:
if (!initFailureReason.empty())
MessageBoxA(NULL, initFailureReason.c_str(), "DeckLink initialization failed", MB_OK | MB_ICONERROR);
mVideoBackend->ReleaseResources();
return false;
}
void OpenGLComposite::paintGL(bool force)
{
if (mRuntimeUpdateController)
mRuntimeUpdateController->ProcessRuntimeWork();
if (!force)
{
if (IsIconic(hGLWnd))
return;
}
const unsigned previewFps = mRuntimeStore ? mRuntimeStore->GetConfiguredPreviewFps() : 30u;
if (!force && mVideoBackend && mVideoBackend->ShouldPrioritizeOutputOverPreview())
{
ValidateRect(hGLWnd, NULL);
return;
}
if (!mRenderEngine->TryPresentPreview(force, previewFps, mVideoBackend->OutputFrameWidth(), mVideoBackend->OutputFrameHeight()))
{
ValidateRect(hGLWnd, NULL);
return;
}
ValidateRect(hGLWnd, NULL);
}
void OpenGLComposite::resizeGL(WORD width, WORD height)
{
// We don't set the project or model matrices here since the window data is copied directly from
// an off-screen FBO in paintGL(). Just save the width and height for use in paintGL().
mRenderEngine->ResizeView(width, height);
}
void OpenGLComposite::resizeWindow(int width, int height)
{
RECT r;
if (GetWindowRect(hGLWnd, &r))
{
SetWindowPos(hGLWnd, HWND_TOP, r.left, r.top, r.left + width, r.top + height, 0);
}
}
bool OpenGLComposite::InitOpenGLState()
{
if (! ResolveGLExtensions())
return false;
std::string runtimeError;
if (mRuntimeStore->GetRuntimeRepositoryRoot().empty() && !mRuntimeStore->InitializeStore(runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime host failed to initialize", MB_OK);
return false;
}
if (!mRuntimeServices->Start(*this, *mRuntimeStore, runtimeError))
{
MessageBoxA(NULL, runtimeError.c_str(), "Runtime control services failed to start", MB_OK);
return false;
}
// Prepare the runtime shader program generated from the active shader package.
char compilerErrorMessage[1024];
if (!mRenderEngine->CompileDecodeShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL decode shader failed to load or compile", MB_OK);
return false;
}
if (!mRenderEngine->CompileOutputPackShader(sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL output pack shader failed to load or compile", MB_OK);
return false;
}
std::string rendererError;
if (!mRenderEngine->InitializeResources(
mVideoBackend->InputFrameWidth(),
mVideoBackend->InputFrameHeight(),
mVideoBackend->CaptureTextureWidth(),
mVideoBackend->OutputFrameWidth(),
mVideoBackend->OutputFrameHeight(),
mVideoBackend->OutputPackTextureWidth(),
rendererError))
{
MessageBoxA(NULL, rendererError.c_str(), "OpenGL initialization error.", MB_OK);
return false;
}
if (!mRenderEngine->CompileLayerPrograms(mVideoBackend->InputFrameWidth(), mVideoBackend->InputFrameHeight(), sizeof(compilerErrorMessage), compilerErrorMessage))
{
MessageBoxA(NULL, compilerErrorMessage, "OpenGL shader failed to load or compile", MB_OK);
return false;
}
mRuntimeStore->SetCompileStatus(true, "Shader layers compiled successfully.");
mRenderEngine->ResetTemporalHistoryState();
mRenderEngine->ResetShaderFeedbackState();
mRuntimeUpdateController->BroadcastRuntimeState();
mRuntimeServices->BeginPolling(*mRuntimeCoordinator);
return true;
}
bool OpenGLComposite::Start()
{
if (!mRenderEngine->StartRenderThread())
return false;
if (mRuntimeUpdateController)
mRuntimeUpdateController->ProcessRuntimeWork();
if (mVideoBackend->Start())
return true;
mRenderEngine->StopRenderThread();
return false;
}
bool OpenGLComposite::Stop()
{
if (mRuntimeServices)
mRuntimeServices->Stop();
const bool wasExternalKeyingActive = mVideoBackend->ExternalKeyingActive();
mVideoBackend->Stop();
if (wasExternalKeyingActive)
mVideoBackend->PublishStatus(
mRuntimeStore && mRuntimeStore->IsExternalKeyingConfigured(),
"External keying has been disabled.");
if (mRenderEngine)
mRenderEngine->StopRenderThread();
if (mRuntimeStore)
{
std::string persistenceError;
if (!mRuntimeStore->FlushPersistenceForShutdown(std::chrono::seconds(2), persistenceError))
OutputDebugStringA((std::string("Persistence shutdown flush failed: ") + persistenceError + "\n").c_str());
}
return true;
}
bool OpenGLComposite::ReloadShader(bool preserveFeedbackState)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RequestShaderReload(preserveFeedbackState));
}
bool OpenGLComposite::RequestScreenshot(std::string& error)
{
if (!mRenderEngine || !mVideoBackend)
{
error = "The render engine is not ready.";
return false;
}
const unsigned width = mVideoBackend->OutputFrameWidth();
const unsigned height = mVideoBackend->OutputFrameHeight();
if (width == 0 || height == 0)
{
error = "The output frame size is not available.";
return false;
}
std::filesystem::path outputPath;
try
{
outputPath = BuildScreenshotPath();
std::filesystem::create_directories(outputPath.parent_path());
}
catch (const std::exception& exception)
{
error = exception.what();
return false;
}
if (!mRenderEngine->RequestScreenshotCapture(
width,
height,
[outputPath](unsigned captureWidth, unsigned captureHeight, std::vector<unsigned char> topDownPixels) {
try
{
WritePngFileAsync(outputPath, captureWidth, captureHeight, std::move(topDownPixels));
}
catch (const std::exception& exception)
{
OutputDebugStringA((std::string("Screenshot request failed: ") + exception.what() + "\n").c_str());
}
}))
{
error = "Screenshot capture request failed.";
return false;
}
return true;
}
void OpenGLComposite::renderEffect()
{
const RenderFrameInput frameInput = BuildRenderFrameInput();
RenderFrame(frameInput);
}
RenderFrameInput OpenGLComposite::BuildRenderFrameInput() const
{
RenderFrameInput frameInput;
frameInput.useCommittedLayerStates = mRuntimeCoordinator && mRuntimeCoordinator->UseCommittedLayerStates();
frameInput.hasInputSource = mVideoBackend->HasInputSource();
frameInput.renderWidth = mVideoBackend->InputFrameWidth();
frameInput.renderHeight = mVideoBackend->InputFrameHeight();
frameInput.inputFrameWidth = mVideoBackend->InputFrameWidth();
frameInput.inputFrameHeight = mVideoBackend->InputFrameHeight();
frameInput.captureTextureWidth = mVideoBackend->CaptureTextureWidth();
frameInput.inputPixelFormat = mVideoBackend->InputPixelFormat();
frameInput.historyCap = mRuntimeStore ? mRuntimeStore->GetConfiguredMaxTemporalHistoryFrames() : 0;
frameInput.oscSmoothing = mRuntimeStore ? mRuntimeStore->GetConfiguredOscSmoothing() : 0.0;
return frameInput;
}
void OpenGLComposite::RenderFrame(const RenderFrameInput& frameInput)
{
RenderFrameState frameState;
if (mRuntimeServices)
{
RuntimeServiceLiveBridge::PrepareLiveRenderFrameState(
*mRuntimeServices,
*mRenderEngine,
frameInput,
frameState);
}
else
{
mRenderEngine->ResolveRenderFrameState(frameInput, nullptr, frameState);
}
mRenderEngine->RenderPreparedFrame(frameState);
}
std::filesystem::path OpenGLComposite::BuildScreenshotPath() const
{
const std::filesystem::path root = mRuntimeStore && !mRuntimeStore->GetRuntimeDataRoot().empty()
? mRuntimeStore->GetRuntimeDataRoot()
: std::filesystem::current_path();
const auto now = std::chrono::system_clock::now();
const auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
const std::time_t nowTime = std::chrono::system_clock::to_time_t(now);
std::tm localTime = {};
localtime_s(&localTime, &nowTime);
std::ostringstream filename;
filename << "video-shader-toys-"
<< std::put_time(&localTime, "%Y%m%d-%H%M%S")
<< "-" << std::setw(3) << std::setfill('0') << milliseconds.count()
<< ".png";
return root / "screenshots" / filename.str();
}
bool OpenGLComposite::CheckOpenGLExtensions()
{
return true;
}

View File

@@ -1,83 +0,0 @@
#ifndef __OPENGL_COMPOSITE_H__
#define __OPENGL_COMPOSITE_H__
#include <windows.h>
#include <objbase.h>
#include "RenderFrameState.h"
#include <filesystem>
#include <memory>
#include <string>
class RenderEngine;
class RuntimeCoordinator;
class RuntimeEventDispatcher;
class RuntimeSnapshotProvider;
class RuntimeServices;
class RuntimeStore;
class RuntimeUpdateController;
class ShaderBuildQueue;
class VideoBackend;
class OpenGLComposite
{
public:
OpenGLComposite(HWND hWnd, HDC hDC, HGLRC hRC);
~OpenGLComposite();
bool InitDeckLink();
bool InitVideoIO();
bool Start();
bool Stop();
bool ReloadShader(bool preserveFeedbackState = false);
std::string GetRuntimeStateJson() const;
bool AddLayer(const std::string& shaderId, std::string& error);
bool RemoveLayer(const std::string& layerId, std::string& error);
bool MoveLayer(const std::string& layerId, int direction, std::string& error);
bool MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error);
bool SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error);
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
bool UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error);
bool UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error);
bool ResetLayerParameters(const std::string& layerId, std::string& error);
bool SaveStackPreset(const std::string& presetName, std::string& error);
bool LoadStackPreset(const std::string& presetName, std::string& error);
bool RequestScreenshot(std::string& error);
unsigned short GetControlServerPort() const;
unsigned short GetOscPort() const;
std::string GetOscBindAddress() const;
std::string GetControlUrl() const;
std::string GetDocsUrl() const;
std::string GetOscAddress() const;
void resizeGL(WORD width, WORD height);
void paintGL(bool force = false);
private:
void resizeWindow(int width, int height);
bool CheckOpenGLExtensions();
HWND hGLWnd;
HDC hGLDC;
HGLRC hGLRC;
std::unique_ptr<RuntimeStore> mRuntimeStore;
std::unique_ptr<RuntimeCoordinator> mRuntimeCoordinator;
std::unique_ptr<RuntimeSnapshotProvider> mRuntimeSnapshotProvider;
std::unique_ptr<RuntimeEventDispatcher> mRuntimeEventDispatcher;
std::unique_ptr<RenderEngine> mRenderEngine;
std::unique_ptr<ShaderBuildQueue> mShaderBuildQueue;
std::unique_ptr<RuntimeServices> mRuntimeServices;
std::unique_ptr<RuntimeUpdateController> mRuntimeUpdateController;
std::unique_ptr<VideoBackend> mVideoBackend;
bool InitOpenGLState();
void renderEffect();
RenderFrameInput BuildRenderFrameInput() const;
void RenderFrame(const RenderFrameInput& frameInput);
std::filesystem::path BuildScreenshotPath() const;
};
#endif // __OPENGL_COMPOSITE_H__

View File

@@ -1,126 +0,0 @@
#include "OpenGLComposite.h"
#include "RuntimeCoordinator.h"
#include "RuntimeJson.h"
#include "RuntimeServices.h"
#include "RuntimeStore.h"
#include "RuntimeUpdateController.h"
std::string OpenGLComposite::GetRuntimeStateJson() const
{
return mRuntimeStore ? mRuntimeStore->BuildPersistentStateJson() : "{}";
}
unsigned short OpenGLComposite::GetControlServerPort() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredControlServerPort() : 0;
}
unsigned short OpenGLComposite::GetOscPort() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscPort() : 0;
}
std::string OpenGLComposite::GetOscBindAddress() const
{
return mRuntimeStore ? mRuntimeStore->GetConfiguredOscBindAddress() : "127.0.0.1";
}
std::string OpenGLComposite::GetControlUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
}
std::string OpenGLComposite::GetDocsUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
}
std::string OpenGLComposite::GetOscAddress() const
{
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
}
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->AddLayer(shaderId), &error);
}
bool OpenGLComposite::RemoveLayer(const std::string& layerId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->RemoveLayer(layerId), &error);
}
bool OpenGLComposite::MoveLayer(const std::string& layerId, int direction, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayer(layerId, direction), &error);
}
bool OpenGLComposite::MoveLayerToIndex(const std::string& layerId, std::size_t targetIndex, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->MoveLayerToIndex(layerId, targetIndex), &error);
}
bool OpenGLComposite::SetLayerBypass(const std::string& layerId, bool bypassed, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerBypass(layerId, bypassed), &error);
}
bool OpenGLComposite::SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SetLayerShader(layerId, shaderId), &error);
}
bool OpenGLComposite::UpdateLayerParameterJson(const std::string& layerId, const std::string& parameterId, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameter(layerId, parameterId, parsedValue), &error);
}
bool OpenGLComposite::UpdateLayerParameterByControlKeyJson(const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& error)
{
JsonValue parsedValue;
if (!ParseJson(valueJson, parsedValue, error))
return false;
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->UpdateLayerParameterByControlKey(layerKey, parameterKey, parsedValue), &error);
}
bool OpenGLComposite::ResetLayerParameters(const std::string& layerId, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->ResetLayerParameters(layerId), &error);
}
bool OpenGLComposite::SaveStackPreset(const std::string& presetName, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->SaveStackPreset(presetName), &error);
}
bool OpenGLComposite::LoadStackPreset(const std::string& presetName, std::string& error)
{
return mRuntimeCoordinator &&
mRuntimeUpdateController &&
mRuntimeUpdateController->ApplyRuntimeCoordinatorResult(mRuntimeCoordinator->LoadStackPreset(presetName), &error);
}

View File

@@ -1,31 +0,0 @@
#pragma once
#include "ShaderTypes.h"
#include "VideoIOTypes.h"
#include <vector>
struct RenderFrameInput
{
bool useCommittedLayerStates = false;
bool hasInputSource = false;
unsigned renderWidth = 0;
unsigned renderHeight = 0;
unsigned inputFrameWidth = 0;
unsigned inputFrameHeight = 0;
unsigned captureTextureWidth = 0;
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
unsigned historyCap = 0;
double oscSmoothing = 0.0;
};
struct RenderFrameState
{
bool hasInputSource = false;
unsigned inputFrameWidth = 0;
unsigned inputFrameHeight = 0;
unsigned captureTextureWidth = 0;
VideoIOPixelFormat inputPixelFormat = VideoIOPixelFormat::Uyvy8;
unsigned historyCap = 0;
std::vector<RuntimeRenderState> layerStates;
};

View File

@@ -1,119 +0,0 @@
#include "RenderFrameStateResolver.h"
#include <chrono>
namespace
{
constexpr auto kOscOverlayCommitDelay = std::chrono::milliseconds(150);
}
RenderFrameStateResolver::RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRuntimeSnapshotProvider(runtimeSnapshotProvider)
{
}
void RenderFrameStateResolver::StoreCommittedSnapshot(
const RuntimeRenderStateSnapshot& snapshot,
const std::vector<RuntimeRenderState>& committedLayerStates)
{
mCachedLayerRenderStates = committedLayerStates;
mCachedRenderStateVersion = snapshot.versions.renderStateVersion;
mCachedParameterStateVersion = snapshot.versions.parameterStateVersion;
mCachedRenderStateWidth = snapshot.outputWidth;
mCachedRenderStateHeight = snapshot.outputHeight;
}
bool RenderFrameStateResolver::Resolve(
const RenderFrameInput& input,
const std::vector<RuntimeRenderState>& committedLayerStates,
RuntimeLiveState& liveState,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
RenderFrameState& frameState)
{
frameState.hasInputSource = input.hasInputSource;
frameState.inputFrameWidth = input.inputFrameWidth;
frameState.inputFrameHeight = input.inputFrameHeight;
frameState.captureTextureWidth = input.captureTextureWidth;
frameState.inputPixelFormat = input.inputPixelFormat;
frameState.historyCap = input.historyCap;
frameState.layerStates.clear();
if (input.useCommittedLayerStates)
{
frameState.layerStates = ComposeLayerStates(committedLayerStates, liveState, false, input.oscSmoothing, commitRequests);
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
return true;
}
const RuntimeSnapshotVersions versions = mRuntimeSnapshotProvider.GetVersions();
const bool renderStateCacheValid =
!mCachedLayerRenderStates.empty() &&
mCachedRenderStateVersion == versions.renderStateVersion &&
mCachedRenderStateWidth == input.renderWidth &&
mCachedRenderStateHeight == input.renderHeight;
if (renderStateCacheValid)
{
RuntimeRenderStateSnapshot renderSnapshot;
renderSnapshot.outputWidth = input.renderWidth;
renderSnapshot.outputHeight = input.renderHeight;
renderSnapshot.versions.renderStateVersion = mCachedRenderStateVersion;
renderSnapshot.versions.parameterStateVersion = mCachedParameterStateVersion;
renderSnapshot.states = mCachedLayerRenderStates;
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
if (mCachedParameterStateVersion != versions.parameterStateVersion &&
mRuntimeSnapshotProvider.TryRefreshPublishedSnapshotParameters(renderSnapshot))
{
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
renderSnapshot.states = ComposeLayerStates(renderSnapshot.states, liveState, true, input.oscSmoothing, commitRequests);
}
mCachedLayerRenderStates = renderSnapshot.states;
frameState.layerStates = renderSnapshot.states;
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
return true;
}
RuntimeRenderStateSnapshot renderSnapshot;
if (mRuntimeSnapshotProvider.TryPublishRenderStateSnapshot(input.renderWidth, input.renderHeight, renderSnapshot))
{
mCachedLayerRenderStates = renderSnapshot.states;
mCachedRenderStateVersion = renderSnapshot.versions.renderStateVersion;
mCachedParameterStateVersion = renderSnapshot.versions.parameterStateVersion;
mCachedRenderStateWidth = renderSnapshot.outputWidth;
mCachedRenderStateHeight = renderSnapshot.outputHeight;
mCachedLayerRenderStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
frameState.layerStates = mCachedLayerRenderStates;
return true;
}
frameState.layerStates = ComposeLayerStates(mCachedLayerRenderStates, liveState, true, input.oscSmoothing, commitRequests);
mRuntimeSnapshotProvider.RefreshDynamicRenderStateFields(frameState.layerStates);
return !frameState.layerStates.empty();
}
std::vector<RuntimeRenderState> RenderFrameStateResolver::ComposeLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
RuntimeLiveState& liveState,
bool allowCommit,
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const
{
LayeredRenderStateInput input;
input.committedLiveLayerStates = &baseStates;
input.transientAutomationOverlay = &liveState;
input.allowTransientAutomationCommits = allowCommit;
input.collectTransientAutomationCommitRequests = commitRequests != nullptr;
input.transientAutomationSmoothing = smoothing;
input.transientAutomationCommitDelay = kOscOverlayCommitDelay;
input.now = std::chrono::steady_clock::now();
const RenderStateCompositionResult result = mRenderStateComposer.BuildFrameState(input);
if (commitRequests)
{
for (const RuntimeLiveOscCommitRequest& request : result.commitRequests)
commitRequests->push_back(request);
}
return result.layerStates;
}

View File

@@ -1,40 +0,0 @@
#pragma once
#include "RenderFrameState.h"
#include "RenderStateComposer.h"
#include "RuntimeSnapshotProvider.h"
#include <cstdint>
#include <vector>
class RenderFrameStateResolver
{
public:
explicit RenderFrameStateResolver(RuntimeSnapshotProvider& runtimeSnapshotProvider);
void StoreCommittedSnapshot(
const RuntimeRenderStateSnapshot& snapshot,
const std::vector<RuntimeRenderState>& committedLayerStates);
bool Resolve(
const RenderFrameInput& input,
const std::vector<RuntimeRenderState>& committedLayerStates,
RuntimeLiveState& liveState,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests,
RenderFrameState& frameState);
private:
std::vector<RuntimeRenderState> ComposeLayerStates(
const std::vector<RuntimeRenderState>& baseStates,
RuntimeLiveState& liveState,
bool allowCommit,
double smoothing,
std::vector<RuntimeLiveOscCommitRequest>* commitRequests) const;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
RenderStateComposer mRenderStateComposer;
std::vector<RuntimeRenderState> mCachedLayerRenderStates;
uint64_t mCachedRenderStateVersion = 0;
uint64_t mCachedParameterStateVersion = 0;
unsigned mCachedRenderStateWidth = 0;
unsigned mCachedRenderStateHeight = 0;
};

View File

@@ -1,378 +0,0 @@
#include "RuntimeUpdateController.h"
#include "RenderEngine.h"
#include "RuntimeEventDispatcher.h"
#include "RuntimeServices.h"
#include "RuntimeStore.h"
#include "ShaderBuildQueue.h"
#include "VideoBackend.h"
#include <variant>
namespace
{
RuntimeCoordinatorRenderResetScope ToRuntimeCoordinatorRenderResetScope(RuntimeEventRenderResetScope scope)
{
switch (scope)
{
case RuntimeEventRenderResetScope::TemporalHistoryOnly:
return RuntimeCoordinatorRenderResetScope::TemporalHistoryOnly;
case RuntimeEventRenderResetScope::TemporalHistoryAndFeedback:
return RuntimeCoordinatorRenderResetScope::TemporalHistoryAndFeedback;
case RuntimeEventRenderResetScope::None:
default:
return RuntimeCoordinatorRenderResetScope::None;
}
}
}
RuntimeUpdateController::RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend) :
mRuntimeStore(runtimeStore),
mRuntimeCoordinator(runtimeCoordinator),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mRuntimeServices(runtimeServices),
mRenderEngine(renderEngine),
mShaderBuildQueue(shaderBuildQueue),
mVideoBackend(videoBackend)
{
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeStateBroadcastRequested,
[this](const RuntimeEvent& event) { HandleRuntimeStateBroadcastRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimeReloadRequested,
[this](const RuntimeEvent& event) { HandleRuntimeReloadRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RuntimePersistenceRequested,
[this](const RuntimeEvent& event) { HandleRuntimePersistenceRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildRequested,
[this](const RuntimeEvent& event) { HandleShaderBuildRequested(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildPrepared,
[this](const RuntimeEvent& event) { HandleShaderBuildPrepared(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::ShaderBuildFailed,
[this](const RuntimeEvent& event) { HandleShaderBuildFailed(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::CompileStatusChanged,
[this](const RuntimeEvent& event) { HandleCompileStatusChanged(event); });
mRuntimeEventDispatcher.Subscribe(
RuntimeEventType::RenderResetRequested,
[this](const RuntimeEvent& event) { HandleRenderResetRequested(event); });
}
bool RuntimeUpdateController::ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error)
{
if (!result.accepted)
{
if (error)
*error = result.errorMessage;
return false;
}
if (result.compileStatusChanged)
{
mRuntimeStore.SetCompileStatus(result.compileStatusSucceeded, result.compileStatusMessage);
++mPendingCoordinatorCompileStatusEvents;
}
if (result.clearReloadRequest)
mRuntimeStore.ClearReloadRequest();
mRuntimeCoordinator.ApplyCommittedStateMode(result.committedStateMode);
switch (result.transientOscInvalidation)
{
case RuntimeCoordinatorTransientOscInvalidation::All:
mRenderEngine.ClearOscOverlayState();
mRuntimeServices.ClearOscState();
break;
case RuntimeCoordinatorTransientOscInvalidation::Layer:
mRenderEngine.ClearOscOverlayStateForLayerKey(result.transientOscLayerKey);
mRuntimeServices.ClearOscStateForLayerKey(result.transientOscLayerKey);
break;
case RuntimeCoordinatorTransientOscInvalidation::None:
default:
break;
}
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(result.renderResetScope);
if (result.renderResetScope != RuntimeCoordinatorRenderResetScope::None)
++mPendingCoordinatorRenderResetEvents;
if (result.shaderBuildRequested)
{
RequestShaderBuild();
++mPendingCoordinatorShaderBuildEvents;
}
if (result.runtimeStateBroadcastRequired)
BroadcastRuntimeState();
return true;
}
bool RuntimeUpdateController::ProcessRuntimeWork()
{
DispatchRuntimeEvents();
return ConsumeReadyShaderBuild(0, true, true);
}
void RuntimeUpdateController::RequestShaderBuild()
{
mShaderBuildQueue.RequestBuild(mVideoBackend.InputFrameWidth(), mVideoBackend.InputFrameHeight());
}
void RuntimeUpdateController::BroadcastRuntimeState()
{
RuntimeStateBroadcastRequestedEvent event;
event.reason = "runtime-state-changed";
if (!mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController"))
{
mRuntimeServices.BroadcastState();
return;
}
DispatchRuntimeEvents();
}
void RuntimeUpdateController::HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event)
{
if (event.source == "ControlServices")
return;
mRuntimeServices.BroadcastState();
}
void RuntimeUpdateController::HandleRuntimeReloadRequested(const RuntimeEvent& event)
{
const RuntimeReloadRequestedEvent* payload = std::get_if<RuntimeReloadRequestedEvent>(&event.payload);
if (!payload)
return;
mRuntimeStore.ClearReloadRequest();
}
void RuntimeUpdateController::HandleRuntimePersistenceRequested(const RuntimeEvent& event)
{
const RuntimePersistenceRequestedEvent* payload = std::get_if<RuntimePersistenceRequestedEvent>(&event.payload);
if (!payload)
return;
std::string error;
mRuntimeStore.RequestPersistence(payload->request, error);
}
void RuntimeUpdateController::HandleShaderBuildRequested(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Requested)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorShaderBuildEvents))
return;
RequestShaderBuild();
}
void RuntimeUpdateController::HandleShaderBuildPrepared(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Prepared)
return;
ConsumeReadyShaderBuild(payload->generation, false, true);
}
void RuntimeUpdateController::HandleShaderBuildFailed(const RuntimeEvent& event)
{
const ShaderBuildEvent* payload = std::get_if<ShaderBuildEvent>(&event.payload);
if (!payload || payload->phase != RuntimeEventShaderBuildPhase::Failed)
return;
ConsumeReadyShaderBuild(payload->generation, false, false);
}
void RuntimeUpdateController::HandleCompileStatusChanged(const RuntimeEvent& event)
{
const CompileStatusChangedEvent* payload = std::get_if<CompileStatusChangedEvent>(&event.payload);
if (!payload)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorCompileStatusEvents))
return;
mRuntimeStore.SetCompileStatus(payload->succeeded, payload->message);
}
void RuntimeUpdateController::HandleRenderResetRequested(const RuntimeEvent& event)
{
const RenderResetEvent* payload = std::get_if<RenderResetEvent>(&event.payload);
if (!payload || payload->applied)
return;
if (ShouldSuppressCoordinatorFollowUp(event, mPendingCoordinatorRenderResetEvents))
return;
mRenderEngine.ApplyRuntimeCoordinatorRenderReset(ToRuntimeCoordinatorRenderResetScope(payload->scope));
}
bool RuntimeUpdateController::ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent)
{
PreparedShaderBuild readyBuild;
const bool consumed = expectedGeneration == 0
? mShaderBuildQueue.TryConsumeReadyBuild(readyBuild)
: mShaderBuildQueue.TryConsumeReadyBuild(expectedGeneration, readyBuild);
if (!consumed)
return true;
const unsigned inputWidth = mVideoBackend.InputFrameWidth();
const unsigned inputHeight = mVideoBackend.InputFrameHeight();
if (!readyBuild.succeeded)
{
if (publishFailureEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Failed,
readyBuild.generation,
inputWidth,
inputHeight,
false,
readyBuild.message);
DispatchRuntimeEvents();
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(readyBuild.message));
return false;
}
if (publishPreparedEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Prepared,
readyBuild.generation,
inputWidth,
inputHeight,
true,
readyBuild.message);
DispatchRuntimeEvents();
}
char compilerErrorMessage[1024] = {};
if (!mRenderEngine.ApplyPreparedShaderBuild(
readyBuild,
inputWidth,
inputHeight,
mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild(),
sizeof(compilerErrorMessage),
compilerErrorMessage))
{
const std::string errorMessage = compilerErrorMessage;
if (publishFailureEvent)
{
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Failed,
readyBuild.generation,
inputWidth,
inputHeight,
false,
errorMessage);
DispatchRuntimeEvents();
}
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildFailure(errorMessage));
return false;
}
PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase::Applied,
readyBuild.generation,
inputWidth,
inputHeight,
true,
"Shader layers applied successfully.");
ApplyRuntimeCoordinatorResult(mRuntimeCoordinator.HandlePreparedShaderBuildSuccess());
return true;
}
void RuntimeUpdateController::PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase phase,
uint64_t generation,
unsigned inputWidth,
unsigned inputHeight,
bool succeeded,
const std::string& message)
{
ShaderBuildEvent event;
event.phase = phase;
event.generation = generation;
event.inputWidth = inputWidth;
event.inputHeight = inputHeight;
event.preserveFeedbackState = mRuntimeCoordinator.PreserveFeedbackOnNextShaderBuild();
event.succeeded = succeeded;
event.message = message;
mRuntimeEventDispatcher.PublishPayload(event, "RuntimeUpdateController");
}
bool RuntimeUpdateController::ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions)
{
if (event.source != "RuntimeCoordinator")
return false;
if (pendingSuppressions > 0)
--pendingSuppressions;
return true;
}
RuntimeEventDispatchResult RuntimeUpdateController::DispatchRuntimeEvents(std::size_t maxEvents)
{
RuntimeEventDispatchResult result = mRuntimeEventDispatcher.DispatchPending(maxEvents);
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
HealthTelemetry& telemetry = mRuntimeStore.GetHealthTelemetry();
telemetry.TryRecordRuntimeEventDispatchStats(
result.dispatchedEvents,
result.handlerInvocations,
result.handlerFailures,
result.dispatchDurationMilliseconds);
telemetry.TryRecordRuntimeEventQueueMetrics(
"runtime-events",
queueMetrics.depth,
queueMetrics.capacity,
static_cast<uint64_t>(queueMetrics.droppedCount),
queueMetrics.oldestEventAgeMilliseconds);
PublishRuntimeEventHealthObservations(result);
return result;
}
void RuntimeUpdateController::PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result)
{
const RuntimeEventQueueMetrics queueMetrics = mRuntimeEventDispatcher.GetQueueMetrics();
if (queueMetrics.depth != mLastReportedRuntimeEventQueueDepth ||
queueMetrics.droppedCount != mLastReportedRuntimeEventDroppedCount ||
queueMetrics.coalescedCount != mLastReportedRuntimeEventCoalescedCount)
{
QueueDepthChangedEvent queueDepth;
queueDepth.queueName = "runtime-events";
queueDepth.depth = queueMetrics.depth;
queueDepth.capacity = queueMetrics.capacity;
queueDepth.droppedCount = queueMetrics.droppedCount;
queueDepth.coalescedCount = queueMetrics.coalescedCount;
mRuntimeEventDispatcher.PublishPayload(queueDepth, "HealthTelemetry");
mLastReportedRuntimeEventQueueDepth = queueMetrics.depth;
mLastReportedRuntimeEventDroppedCount = queueMetrics.droppedCount;
mLastReportedRuntimeEventCoalescedCount = queueMetrics.coalescedCount;
}
if (result.handlerInvocations == 0 && result.handlerFailures == 0)
return;
TimingSampleRecordedEvent timing;
timing.subsystem = "RuntimeEventDispatcher";
timing.metric = "dispatchDuration";
timing.value = result.dispatchDurationMilliseconds;
timing.unit = "ms";
mRuntimeEventDispatcher.PublishPayload(timing, "HealthTelemetry");
}

View File

@@ -1,70 +0,0 @@
#pragma once
#include "RuntimeCoordinator.h"
#include "RuntimeEventPayloads.h"
#include <cstddef>
#include <cstdint>
#include <string>
class RenderEngine;
struct RuntimeEvent;
struct RuntimeEventDispatchResult;
class RuntimeEventDispatcher;
class RuntimeServices;
class RuntimeStore;
class ShaderBuildQueue;
class VideoBackend;
class RuntimeUpdateController
{
public:
RuntimeUpdateController(
RuntimeStore& runtimeStore,
RuntimeCoordinator& runtimeCoordinator,
RuntimeEventDispatcher& runtimeEventDispatcher,
RuntimeServices& runtimeServices,
RenderEngine& renderEngine,
ShaderBuildQueue& shaderBuildQueue,
VideoBackend& videoBackend);
bool ApplyRuntimeCoordinatorResult(const RuntimeCoordinatorResult& result, std::string* error = nullptr);
bool ProcessRuntimeWork();
void RequestShaderBuild();
void BroadcastRuntimeState();
private:
void HandleRuntimeStateBroadcastRequested(const RuntimeEvent& event);
void HandleRuntimeReloadRequested(const RuntimeEvent& event);
void HandleRuntimePersistenceRequested(const RuntimeEvent& event);
void HandleShaderBuildRequested(const RuntimeEvent& event);
void HandleShaderBuildPrepared(const RuntimeEvent& event);
void HandleShaderBuildFailed(const RuntimeEvent& event);
void HandleCompileStatusChanged(const RuntimeEvent& event);
void HandleRenderResetRequested(const RuntimeEvent& event);
bool ConsumeReadyShaderBuild(uint64_t expectedGeneration, bool publishPreparedEvent, bool publishFailureEvent);
void PublishShaderBuildLifecycleEvent(
RuntimeEventShaderBuildPhase phase,
uint64_t generation,
unsigned inputWidth,
unsigned inputHeight,
bool succeeded,
const std::string& message);
bool ShouldSuppressCoordinatorFollowUp(const RuntimeEvent& event, std::size_t& pendingSuppressions);
RuntimeEventDispatchResult DispatchRuntimeEvents(std::size_t maxEvents = 0);
void PublishRuntimeEventHealthObservations(const RuntimeEventDispatchResult& result);
RuntimeStore& mRuntimeStore;
RuntimeCoordinator& mRuntimeCoordinator;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
RuntimeServices& mRuntimeServices;
RenderEngine& mRenderEngine;
ShaderBuildQueue& mShaderBuildQueue;
VideoBackend& mVideoBackend;
std::size_t mPendingCoordinatorShaderBuildEvents = 0;
std::size_t mPendingCoordinatorCompileStatusEvents = 0;
std::size_t mPendingCoordinatorRenderResetEvents = 0;
std::size_t mLastReportedRuntimeEventQueueDepth = static_cast<std::size_t>(-1);
std::size_t mLastReportedRuntimeEventDroppedCount = static_cast<std::size_t>(-1);
std::size_t mLastReportedRuntimeEventCoalescedCount = static_cast<std::size_t>(-1);
};

View File

@@ -1,288 +0,0 @@
#include "OpenGLRenderPass.h"
#include "GlRenderConstants.h"
#include <map>
OpenGLRenderPass::OpenGLRenderPass(OpenGLRenderer& renderer) :
mRenderer(renderer)
{
}
void OpenGLRenderPass::Render(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap,
const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams)
{
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
if (hasInputSource)
{
RenderDecodePass(inputFrameWidth, inputFrameHeight, captureTextureWidth, inputPixelFormat);
}
else
{
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
std::vector<LayerProgram>& layerPrograms = mRenderer.LayerPrograms();
if (layerStates.empty() || layerPrograms.empty())
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
glBlitFramebuffer(0, 0, inputFrameWidth, inputFrameHeight, 0, 0, inputFrameWidth, inputFrameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
}
else
{
const std::vector<RenderPassDescriptor>& passes = BuildLayerPassDescriptors(layerStates, layerPrograms);
for (const RenderPassDescriptor& pass : passes)
{
RenderLayerPass(
pass,
inputFrameWidth,
inputFrameHeight,
historyCap,
updateTextBinding,
updateGlobalParams);
}
}
mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
mRenderer.FeedbackBuffers().FinalizeFrame();
}
void OpenGLRenderPass::RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat)
{
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.DecodeFramebuffer());
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0 + kPackedVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture());
glBindVertexArray(mRenderer.FullscreenVertexArray());
glUseProgram(mRenderer.DecodeProgram());
const GLint packedResolutionLocation = mRenderer.DecodePackedResolutionLocation();
const GLint decodedResolutionLocation = mRenderer.DecodeDecodedResolutionLocation();
const GLint inputPixelFormatLocation = mRenderer.DecodeInputPixelFormatLocation();
if (packedResolutionLocation >= 0)
glUniform2f(packedResolutionLocation, static_cast<float>(captureTextureWidth), static_cast<float>(inputFrameHeight));
if (decodedResolutionLocation >= 0)
glUniform2f(decodedResolutionLocation, static_cast<float>(inputFrameWidth), static_cast<float>(inputFrameHeight));
if (inputPixelFormatLocation >= 0)
glUniform1i(inputPixelFormatLocation, inputPixelFormat == VideoIOPixelFormat::V210 ? 1 : 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
}
std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
const std::vector<RuntimeRenderState>& layerStates,
std::vector<LayerProgram>& layerPrograms) const
{
// Flatten the layer stack into concrete GL passes. A layer may now contain
// several shader passes, but the outer stack still sees one visible output
// per layer.
std::vector<RenderPassDescriptor>& passes = mPassScratch;
passes.clear();
const std::size_t passCount = layerStates.size() < layerPrograms.size() ? layerStates.size() : layerPrograms.size();
std::size_t descriptorCount = 0;
for (std::size_t index = 0; index < passCount; ++index)
descriptorCount += layerPrograms[index].passes.size();
passes.reserve(descriptorCount);
GLuint sourceTexture = mRenderer.DecodedTexture();
GLuint sourceFramebuffer = mRenderer.DecodeFramebuffer();
for (std::size_t index = 0; index < passCount; ++index)
{
const RuntimeRenderState& state = layerStates[index];
LayerProgram& layerProgram = layerPrograms[index];
if (layerProgram.passes.empty())
continue;
// Preserve the original two-target layer ping-pong. Intermediate passes
// inside this layer are routed through pooled temporary targets instead.
const std::size_t remaining = layerStates.size() - index;
const bool writeToMain = (remaining % 2) == 1;
const GLuint layerOutputTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture();
const GLuint layerOutputFramebuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer();
const RenderPassOutputTarget layerOutputTarget = writeToMain ? RenderPassOutputTarget::Composite : RenderPassOutputTarget::LayerTemp;
const GLuint layerInputTexture = sourceTexture;
const GLuint layerInputFramebuffer = sourceFramebuffer;
GLuint previousPassTexture = layerInputTexture;
GLuint previousPassFramebuffer = layerInputFramebuffer;
std::map<std::string, std::pair<GLuint, GLuint>> namedOutputs;
std::size_t temporaryTargetIndex = 0;
for (std::size_t passIndex = 0; passIndex < layerProgram.passes.size(); ++passIndex)
{
PassProgram& passProgram = layerProgram.passes[passIndex];
const bool lastPassForLayer = passIndex + 1 == layerProgram.passes.size();
const std::string outputName = passProgram.outputName.empty() ? passProgram.passId : passProgram.outputName;
const bool writesLayerOutput = outputName == "layerOutput" || lastPassForLayer;
GLuint passSourceTexture = previousPassTexture;
GLuint passSourceFramebuffer = previousPassFramebuffer;
if (!passProgram.inputNames.empty())
{
// v1 multipass uses the first declared input as gVideoInput.
// Later inputs are parsed for forward compatibility.
const std::string& inputName = passProgram.inputNames.front();
if (inputName == "layerInput")
{
passSourceTexture = layerInputTexture;
passSourceFramebuffer = layerInputFramebuffer;
}
else if (inputName == "previousPass")
{
passSourceTexture = previousPassTexture;
passSourceFramebuffer = previousPassFramebuffer;
}
else
{
auto namedOutputIt = namedOutputs.find(inputName);
if (namedOutputIt != namedOutputs.end())
{
passSourceTexture = namedOutputIt->second.first;
passSourceFramebuffer = namedOutputIt->second.second;
}
}
}
GLuint passDestinationTexture = layerOutputTexture;
GLuint passDestinationFramebuffer = layerOutputFramebuffer;
RenderPassOutputTarget outputTarget = layerOutputTarget;
if (!writesLayerOutput)
{
// Temporary targets are reserved when the shader stack is
// committed, avoiding texture allocation during playback.
if (temporaryTargetIndex < mRenderer.TemporaryRenderTargetCount())
{
const RenderTarget& temporaryTarget = mRenderer.TemporaryRenderTarget(temporaryTargetIndex);
++temporaryTargetIndex;
passDestinationTexture = temporaryTarget.texture;
passDestinationFramebuffer = temporaryTarget.framebuffer;
outputTarget = RenderPassOutputTarget::Temporary;
}
}
RenderPassDescriptor pass;
pass.kind = RenderPassKind::LayerEffect;
pass.outputTarget = outputTarget;
pass.passIndex = passes.size();
pass.passId = passProgram.passId;
pass.layerId = state.layerId;
pass.shaderId = state.shaderId;
pass.layerInputTexture = layerInputTexture;
pass.sourceTexture = passSourceTexture;
pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer;
pass.destinationTexture = passDestinationTexture;
pass.destinationFramebuffer = passDestinationFramebuffer;
pass.layerProgram = &layerProgram;
pass.passProgram = &passProgram;
pass.layerState = &state;
pass.capturePreLayerHistory = passIndex == 0 && state.temporalHistorySource == TemporalHistorySource::PreLayerInput;
pass.captureFeedbackWrite = state.feedback.enabled && passProgram.passId == state.feedback.writePassId;
passes.push_back(pass);
// A later pass can reference either the explicit output name or the
// pass id, which keeps small manifests pleasant to write.
namedOutputs[outputName] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
namedOutputs[passProgram.passId] = std::make_pair(passDestinationTexture, passDestinationFramebuffer);
previousPassTexture = passDestinationTexture;
previousPassFramebuffer = passDestinationFramebuffer;
}
sourceTexture = layerOutputTexture;
sourceFramebuffer = layerOutputFramebuffer;
}
return passes;
}
void OpenGLRenderPass::RenderLayerPass(
const RenderPassDescriptor& pass,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned historyCap,
const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams)
{
if (pass.passProgram == nullptr || pass.layerState == nullptr)
return;
RenderShaderProgram(
pass.layerInputTexture,
pass.sourceTexture,
pass.destinationFramebuffer,
*pass.passProgram,
*pass.layerState,
inputFrameWidth,
inputFrameHeight,
historyCap,
updateTextBinding,
updateGlobalParams);
if (pass.capturePreLayerHistory)
mRenderer.TemporalHistory().PushPreLayerFramebuffer(pass.layerId, pass.sourceFramebuffer, inputFrameWidth, inputFrameHeight);
if (pass.captureFeedbackWrite)
mRenderer.FeedbackBuffers().CaptureFeedbackFramebuffer(pass.layerId, pass.destinationFramebuffer, inputFrameWidth, inputFrameHeight);
}
void OpenGLRenderPass::RenderShaderProgram(
GLuint layerInputTexture,
GLuint sourceTexture,
GLuint destinationFrameBuffer,
PassProgram& passProgram,
const RuntimeRenderState& state,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned historyCap,
const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams)
{
for (LayerProgram::TextBinding& textBinding : passProgram.textBindings)
{
std::string textError;
if (!updateTextBinding(state, textBinding, textError))
OutputDebugStringA((textError + "\n").c_str());
}
glBindFramebuffer(GL_FRAMEBUFFER, destinationFrameBuffer);
glViewport(0, 0, inputFrameWidth, inputFrameHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
const std::vector<GLuint> sourceHistoryTextures = mRenderer.TemporalHistory().ResolveSourceHistoryTextures(sourceTexture, state.isTemporal ? historyCap : 0);
const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0);
const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state);
const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan =
mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, layerInputTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures);
mTextureBindings.BindRuntimeTexturePlan(texturePlan);
glBindVertexArray(mRenderer.FullscreenVertexArray());
glUseProgram(passProgram.program);
// The UBO is shared by every pass in a layer; texture routing is what
// changes from pass to pass.
updateGlobalParams(
state,
mRenderer.TemporalHistory().SourceAvailableCount(),
mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId),
mRenderer.FeedbackBuffers().FeedbackAvailable(state));
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
mTextureBindings.UnbindRuntimeTexturePlan(texturePlan);
}

View File

@@ -1,61 +0,0 @@
#pragma once
#include "OpenGLRenderer.h"
#include "RenderPassDescriptor.h"
#include "ShaderTextureBindings.h"
#include "ShaderTypes.h"
#include "VideoIOFormat.h"
#include <functional>
#include <string>
#include <vector>
class OpenGLRenderPass
{
public:
using LayerProgram = OpenGLRenderer::LayerProgram;
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>;
using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned, bool)>;
explicit OpenGLRenderPass(OpenGLRenderer& renderer);
void Render(
bool hasInputSource,
const std::vector<RuntimeRenderState>& layerStates,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned captureTextureWidth,
VideoIOPixelFormat inputPixelFormat,
unsigned historyCap,
const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams);
private:
void RenderDecodePass(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, VideoIOPixelFormat inputPixelFormat);
std::vector<RenderPassDescriptor> BuildLayerPassDescriptors(
const std::vector<RuntimeRenderState>& layerStates,
std::vector<LayerProgram>& layerPrograms) const;
void RenderLayerPass(
const RenderPassDescriptor& pass,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned historyCap,
const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams);
void RenderShaderProgram(
GLuint layerInputTexture,
GLuint sourceTexture,
GLuint destinationFrameBuffer,
PassProgram& passProgram,
const RuntimeRenderState& state,
unsigned inputFrameWidth,
unsigned inputFrameHeight,
unsigned historyCap,
const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams);
OpenGLRenderer& mRenderer;
ShaderTextureBindings mTextureBindings;
mutable std::vector<RenderPassDescriptor> mPassScratch;
};

View File

@@ -1,479 +0,0 @@
#include "OpenGLRenderPipeline.h"
#include "HealthTelemetry.h"
#include "OpenGLRenderer.h"
#include "RuntimeSnapshotProvider.h"
#include "VideoIOFormat.h"
#include <cstring>
#include <chrono>
#include <cstdlib>
#include <gl/gl.h>
#include <string>
OpenGLRenderPipeline::OpenGLRenderPipeline(
OpenGLRenderer& renderer,
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
RenderEffectCallback renderEffect,
OutputReadyCallback outputReady,
PaintCallback paint) :
mRenderer(renderer),
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mHealthTelemetry(healthTelemetry),
mRenderEffect(renderEffect),
mOutputReady(outputReady),
mPaint(paint),
mOutputReadbackMode(ReadOutputReadbackModeFromEnvironment()),
mAsyncReadbackDepth(ReadAsyncReadbackDepthFromEnvironment())
{
}
OpenGLRenderPipeline::~OpenGLRenderPipeline()
{
ResetAsyncReadbackState();
}
bool OpenGLRenderPipeline::RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame)
{
const VideoIOState& state = context.videoState;
const auto renderStartTime = std::chrono::steady_clock::now();
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
mRenderEffect();
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.CompositeFramebuffer());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glBlitFramebuffer(0, 0, state.inputFrameSize.width, state.inputFrameSize.height, 0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputFramebuffer());
if (mOutputReady)
mOutputReady();
if (state.outputPixelFormat == VideoIOPixelFormat::Bgra8)
PackOutputForBgra8(state);
else if (state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10)
PackOutputFor10Bit(state);
glFlush();
const auto renderEndTime = std::chrono::steady_clock::now();
const double renderMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(renderEndTime - renderStartTime).count();
mHealthTelemetry.TryRecordPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
mRuntimeSnapshotProvider.AdvanceFrame();
OutputReadbackTiming readbackTiming = ReadOutputFrame(state, outputFrame);
mHealthTelemetry.TryRecordOutputRenderPipelineTiming(
renderMilliseconds,
readbackTiming.fenceWaitMilliseconds,
readbackTiming.mapMilliseconds,
readbackTiming.copyMilliseconds,
readbackTiming.cachedCopyMilliseconds,
readbackTiming.asyncQueueMilliseconds,
readbackTiming.asyncQueueBufferMilliseconds,
readbackTiming.asyncQueueSetupMilliseconds,
readbackTiming.asyncQueueReadPixelsMilliseconds,
readbackTiming.asyncQueueFenceMilliseconds,
readbackTiming.syncReadMilliseconds,
readbackTiming.asyncReadbackMissed,
readbackTiming.cachedFallbackUsed,
readbackTiming.syncFallbackUsed);
return true;
}
void OpenGLRenderPipeline::PackOutputForBgra8(const VideoIOState& state)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
glBlitFramebuffer(
0,
0,
state.outputFrameSize.width,
state.outputFrameSize.height,
0,
0,
state.outputFrameSize.width,
state.outputFrameSize.height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
}
void OpenGLRenderPipeline::PackOutputFor10Bit(const VideoIOState& state)
{
glBindFramebuffer(GL_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
glViewport(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mRenderer.OutputTexture());
glBindVertexArray(mRenderer.FullscreenVertexArray());
glUseProgram(mRenderer.OutputPackProgram());
const GLint outputResolutionLocation = mRenderer.OutputPackResolutionLocation();
const GLint activeWordsLocation = mRenderer.OutputPackActiveWordsLocation();
const GLint packFormatLocation = mRenderer.OutputPackFormatLocation();
if (outputResolutionLocation >= 0)
glUniform2f(outputResolutionLocation, static_cast<float>(state.outputFrameSize.width), static_cast<float>(state.outputFrameSize.height));
if (activeWordsLocation >= 0)
glUniform1f(activeWordsLocation, static_cast<float>(ActiveV210WordsForWidth(state.outputFrameSize.width)));
if (packFormatLocation >= 0)
glUniform1i(packFormatLocation, state.outputPixelFormat == VideoIOPixelFormat::Yuva10 ? 2 : 1);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
bool OpenGLRenderPipeline::EnsureAsyncReadbackBuffers(std::size_t requiredBytes)
{
if (requiredBytes == 0)
return false;
if (mAsyncReadbackBytes == requiredBytes &&
mAsyncReadbackSlots.size() == mAsyncReadbackDepth &&
!mAsyncReadbackSlots.empty() &&
mAsyncReadbackSlots[0].pixelPackBuffer != 0)
{
return true;
}
ResetAsyncReadbackState();
mAsyncReadbackBytes = requiredBytes;
mAsyncReadbackSlots.resize(mAsyncReadbackDepth);
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
{
glGenBuffers(1, &slot.pixelPackBuffer);
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
slot.sizeBytes = requiredBytes;
slot.inFlight = false;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mAsyncReadbackWriteIndex = 0;
mAsyncReadbackReadIndex = 0;
return true;
}
void OpenGLRenderPipeline::ResetAsyncReadbackState()
{
FlushAsyncReadbackPipeline();
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
slot.sizeBytes = 0;
if (!mAsyncReadbackSlots.empty() && mAsyncReadbackSlots[0].pixelPackBuffer != 0)
{
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
{
if (slot.pixelPackBuffer != 0)
{
glDeleteBuffers(1, &slot.pixelPackBuffer);
slot.pixelPackBuffer = 0;
}
}
}
mAsyncReadbackWriteIndex = 0;
mAsyncReadbackReadIndex = 0;
mAsyncReadbackBytes = 0;
mAsyncReadbackSlots.clear();
}
void OpenGLRenderPipeline::FlushAsyncReadbackPipeline()
{
for (AsyncReadbackSlot& slot : mAsyncReadbackSlots)
{
if (slot.fence != nullptr)
{
glDeleteSync(slot.fence);
slot.fence = nullptr;
}
slot.inFlight = false;
}
mAsyncReadbackWriteIndex = 0;
mAsyncReadbackReadIndex = 0;
}
bool OpenGLRenderPipeline::QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing)
{
const auto queueStartTime = std::chrono::steady_clock::now();
const bool useTenBitPackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 ||
state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || useTenBitPackedOutput;
const std::size_t requiredBytes = static_cast<std::size_t>(state.outputFrameRowBytes) * state.outputFrameSize.height;
const GLenum format = useTenBitPackedOutput ? GL_RGBA : GL_BGRA;
const GLenum type = useTenBitPackedOutput ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_8_8_8_8_REV;
const GLuint framebuffer = usePackFramebuffer ? mRenderer.OutputPackFramebuffer() : mRenderer.OutputFramebuffer();
const GLsizei readWidth = static_cast<GLsizei>(useTenBitPackedOutput ? state.outputPackTextureWidth : state.outputFrameSize.width);
const GLsizei readHeight = static_cast<GLsizei>(state.outputFrameSize.height);
const auto finishTiming = [&timing, queueStartTime]() {
const auto queueEndTime = std::chrono::steady_clock::now();
timing.asyncQueueMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(queueEndTime - queueStartTime).count();
};
if (requiredBytes == 0)
{
finishTiming();
return false;
}
if (mAsyncReadbackBytes != requiredBytes
|| mAsyncReadbackFormat != format
|| mAsyncReadbackType != type
|| mAsyncReadbackFramebuffer != framebuffer)
{
mAsyncReadbackFormat = format;
mAsyncReadbackType = type;
mAsyncReadbackFramebuffer = framebuffer;
if (!EnsureAsyncReadbackBuffers(requiredBytes))
{
finishTiming();
return false;
}
}
if (mAsyncReadbackSlots.empty())
{
finishTiming();
return false;
}
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackWriteIndex];
if (slot.inFlight)
{
finishTiming();
return false;
}
auto stageStartTime = std::chrono::steady_clock::now();
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
auto stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueSetupMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
stageStartTime = std::chrono::steady_clock::now();
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(requiredBytes), nullptr, GL_STREAM_READ);
stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueBufferMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
stageStartTime = std::chrono::steady_clock::now();
glReadPixels(0, 0, readWidth, readHeight, format, type, nullptr);
stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueReadPixelsMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
stageStartTime = std::chrono::steady_clock::now();
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
stageEndTime = std::chrono::steady_clock::now();
timing.asyncQueueFenceMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(stageEndTime - stageStartTime).count();
slot.inFlight = slot.fence != nullptr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
mAsyncReadbackWriteIndex = (mAsyncReadbackWriteIndex + 1) % mAsyncReadbackSlots.size();
finishTiming();
return slot.inFlight;
}
bool OpenGLRenderPipeline::TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing)
{
if (mAsyncReadbackBytes == 0 || outputFrame.bytes == nullptr)
return false;
AsyncReadbackSlot& slot = mAsyncReadbackSlots[mAsyncReadbackReadIndex];
if (!slot.inFlight || slot.fence == nullptr || slot.pixelPackBuffer == 0)
return false;
const GLenum waitFlags = timeoutNanoseconds > 0 ? GL_SYNC_FLUSH_COMMANDS_BIT : 0;
const auto waitStartTime = std::chrono::steady_clock::now();
const GLenum waitResult = glClientWaitSync(slot.fence, waitFlags, timeoutNanoseconds);
const auto waitEndTime = std::chrono::steady_clock::now();
timing.fenceWaitMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(waitEndTime - waitStartTime).count();
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
{
timing.asyncReadbackMissed = true;
return false;
}
glDeleteSync(slot.fence);
slot.fence = nullptr;
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pixelPackBuffer);
const auto mapStartTime = std::chrono::steady_clock::now();
void* mappedBytes = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
const auto mapEndTime = std::chrono::steady_clock::now();
timing.mapMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(mapEndTime - mapStartTime).count();
if (mappedBytes == nullptr)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
slot.inFlight = false;
mAsyncReadbackReadIndex = (mAsyncReadbackReadIndex + 1) % mAsyncReadbackSlots.size();
return false;
}
const auto copyStartTime = std::chrono::steady_clock::now();
std::memcpy(outputFrame.bytes, mappedBytes, slot.sizeBytes);
const auto copyEndTime = std::chrono::steady_clock::now();
timing.copyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
slot.inFlight = false;
mAsyncReadbackReadIndex = (mAsyncReadbackReadIndex + 1) % mAsyncReadbackSlots.size();
CacheOutputFrame(outputFrame);
return true;
}
void OpenGLRenderPipeline::CacheOutputFrame(const VideoIOOutputFrame& outputFrame)
{
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
return;
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
mCachedOutputFrame.resize(byteCount);
std::memcpy(mCachedOutputFrame.data(), outputFrame.bytes, byteCount);
}
bool OpenGLRenderPipeline::TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const
{
if (outputFrame.bytes == nullptr || outputFrame.height == 0 || outputFrame.rowBytes <= 0)
return false;
const std::size_t byteCount = static_cast<std::size_t>(outputFrame.rowBytes) * outputFrame.height;
if (mCachedOutputFrame.size() != byteCount)
return false;
const auto copyStartTime = std::chrono::steady_clock::now();
std::memcpy(outputFrame.bytes, mCachedOutputFrame.data(), byteCount);
const auto copyEndTime = std::chrono::steady_clock::now();
timing.cachedCopyMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(copyEndTime - copyStartTime).count();
timing.cachedFallbackUsed = true;
return true;
}
void OpenGLRenderPipeline::ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing)
{
const auto readStartTime = std::chrono::steady_clock::now();
const bool usePackedOutput = state.outputPixelFormat == VideoIOPixelFormat::V210 || state.outputPixelFormat == VideoIOPixelFormat::Yuva10;
const bool usePackFramebuffer = state.outputPixelFormat == VideoIOPixelFormat::Bgra8 || usePackedOutput;
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
if (usePackFramebuffer)
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
if (usePackedOutput)
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, destinationBytes);
else
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
}
else
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer());
glReadPixels(0, 0, state.outputFrameSize.width, state.outputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, destinationBytes);
}
const auto readEndTime = std::chrono::steady_clock::now();
timing.syncReadMilliseconds += std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(readEndTime - readStartTime).count();
timing.syncFallbackUsed = true;
}
OpenGLRenderPipeline::OutputReadbackTiming OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
{
OutputReadbackTiming timing;
if (mOutputReadbackMode == OutputReadbackMode::Synchronous)
{
if (outputFrame.bytes != nullptr)
{
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
CacheOutputFrame(outputFrame);
}
return timing;
}
if (mOutputReadbackMode == OutputReadbackMode::CachedOnly)
{
if (TryCopyCachedOutputFrame(outputFrame, timing))
return timing;
if (outputFrame.bytes != nullptr)
{
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
CacheOutputFrame(outputFrame);
}
return timing;
}
if (TryConsumeAsyncReadback(outputFrame, 0, timing))
{
(void)QueueAsyncReadback(state, timing);
return timing;
}
const bool queued = QueueAsyncReadback(state, timing);
if (queued && TryConsumeAsyncReadback(outputFrame, 0, timing))
return timing;
if (TryCopyCachedOutputFrame(outputFrame, timing))
{
return timing;
}
// Bootstrap only: until the first async readback has produced cached output,
// use one synchronous readback so DeckLink has a valid frame to schedule.
if (outputFrame.bytes != nullptr && mCachedOutputFrame.empty())
{
ReadOutputFrameSynchronously(state, outputFrame.bytes, timing);
CacheOutputFrame(outputFrame);
}
if (!queued)
(void)QueueAsyncReadback(state, timing);
return timing;
}
OpenGLRenderPipeline::OutputReadbackMode OpenGLRenderPipeline::ReadOutputReadbackModeFromEnvironment()
{
char* mode = nullptr;
std::size_t modeSize = 0;
if (_dupenv_s(&mode, &modeSize, "VST_OUTPUT_READBACK_MODE") != 0 || mode == nullptr)
return OutputReadbackMode::AsyncPbo;
const std::string modeValue(mode);
std::free(mode);
if (modeValue == "async_pbo")
return OutputReadbackMode::AsyncPbo;
if (modeValue == "sync")
return OutputReadbackMode::Synchronous;
if (modeValue == "cached_only")
return OutputReadbackMode::CachedOnly;
return OutputReadbackMode::AsyncPbo;
}
std::size_t OpenGLRenderPipeline::ReadAsyncReadbackDepthFromEnvironment()
{
char* depthValue = nullptr;
std::size_t depthValueSize = 0;
if (_dupenv_s(&depthValue, &depthValueSize, "VST_OUTPUT_READBACK_DEPTH") != 0 || depthValue == nullptr)
return 6;
const std::string value(depthValue);
std::free(depthValue);
try
{
const unsigned long requestedDepth = std::stoul(value);
if (requestedDepth < 3)
return 3;
if (requestedDepth > 12)
return 12;
return static_cast<std::size_t>(requestedDepth);
}
catch (...)
{
return 6;
}
}

View File

@@ -1,100 +0,0 @@
#pragma once
#include "GLExtensions.h"
#include "VideoIOTypes.h"
#include <functional>
#include <vector>
class OpenGLRenderer;
class HealthTelemetry;
class RuntimeSnapshotProvider;
struct RenderPipelineFrameContext
{
VideoIOState videoState;
VideoIOCompletion completion;
};
class OpenGLRenderPipeline
{
public:
using RenderEffectCallback = std::function<void()>;
using OutputReadyCallback = std::function<void()>;
using PaintCallback = std::function<void()>;
OpenGLRenderPipeline(
OpenGLRenderer& renderer,
RuntimeSnapshotProvider& runtimeSnapshotProvider,
HealthTelemetry& healthTelemetry,
RenderEffectCallback renderEffect,
OutputReadyCallback outputReady,
PaintCallback paint);
~OpenGLRenderPipeline();
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
private:
enum class OutputReadbackMode
{
AsyncPbo,
Synchronous,
CachedOnly
};
struct AsyncReadbackSlot
{
GLuint pixelPackBuffer = 0;
GLsync fence = nullptr;
std::size_t sizeBytes = 0;
bool inFlight = false;
};
struct OutputReadbackTiming
{
double fenceWaitMilliseconds = 0.0;
double mapMilliseconds = 0.0;
double copyMilliseconds = 0.0;
double cachedCopyMilliseconds = 0.0;
double asyncQueueMilliseconds = 0.0;
double asyncQueueBufferMilliseconds = 0.0;
double asyncQueueSetupMilliseconds = 0.0;
double asyncQueueReadPixelsMilliseconds = 0.0;
double asyncQueueFenceMilliseconds = 0.0;
double syncReadMilliseconds = 0.0;
bool asyncReadbackMissed = false;
bool cachedFallbackUsed = false;
bool syncFallbackUsed = false;
};
bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
void ResetAsyncReadbackState();
void FlushAsyncReadbackPipeline();
bool QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing);
bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing);
void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
bool TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const;
void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing);
void PackOutputForBgra8(const VideoIOState& state);
void PackOutputFor10Bit(const VideoIOState& state);
OutputReadbackTiming ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
static OutputReadbackMode ReadOutputReadbackModeFromEnvironment();
static std::size_t ReadAsyncReadbackDepthFromEnvironment();
OpenGLRenderer& mRenderer;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
HealthTelemetry& mHealthTelemetry;
RenderEffectCallback mRenderEffect;
OutputReadyCallback mOutputReady;
PaintCallback mPaint;
OutputReadbackMode mOutputReadbackMode = OutputReadbackMode::AsyncPbo;
std::vector<AsyncReadbackSlot> mAsyncReadbackSlots;
std::size_t mAsyncReadbackDepth = 0;
std::size_t mAsyncReadbackWriteIndex = 0;
std::size_t mAsyncReadbackReadIndex = 0;
std::size_t mAsyncReadbackBytes = 0;
GLenum mAsyncReadbackFormat = GL_BGRA;
GLenum mAsyncReadbackType = GL_UNSIGNED_INT_8_8_8_8_REV;
GLuint mAsyncReadbackFramebuffer = 0;
std::vector<unsigned char> mCachedOutputFrame;
};

View File

@@ -1,25 +0,0 @@
#include "OpenGLVideoIOBridge.h"
#include "RenderEngine.h"
OpenGLVideoIOBridge::OpenGLVideoIOBridge(RenderEngine& renderEngine) :
mRenderEngine(renderEngine)
{
}
void OpenGLVideoIOBridge::UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state)
{
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
return; // don't transfer texture when there's no input
mRenderEngine.QueueInputFrame(inputFrame, state);
}
bool OpenGLVideoIOBridge::RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame)
{
RenderPipelineFrameContext frameContext;
frameContext.videoState = state;
frameContext.completion = completion;
return mRenderEngine.RequestOutputFrame(frameContext, outputFrame);
}

View File

@@ -1,17 +0,0 @@
#pragma once
#include "OpenGLRenderPipeline.h"
class RenderEngine;
class OpenGLVideoIOBridge
{
public:
explicit OpenGLVideoIOBridge(RenderEngine& renderEngine);
void UploadInputFrame(const VideoIOFrame& inputFrame, const VideoIOState& state);
bool RenderScheduledFrame(const VideoIOState& state, const VideoIOCompletion& completion, VideoIOOutputFrame& outputFrame);
private:
RenderEngine& mRenderEngine;
};

View File

@@ -1,137 +0,0 @@
#include "PngScreenshotWriter.h"
#include <windows.h>
#include <wincodec.h>
#include <atlbase.h>
#include <sstream>
#include <thread>
namespace
{
std::string HResultToString(HRESULT hr)
{
std::ostringstream stream;
stream << "HRESULT 0x" << std::hex << static_cast<unsigned long>(hr);
return stream.str();
}
bool WritePngFile(
const std::filesystem::path& outputPath,
unsigned width,
unsigned height,
const std::vector<unsigned char>& bgraPixels,
std::string& error)
{
if (width == 0 || height == 0 || bgraPixels.size() < static_cast<std::size_t>(width) * height * 4)
{
error = "Invalid screenshot dimensions or pixel buffer.";
return false;
}
HRESULT initializeResult = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
const bool shouldUninitialize = SUCCEEDED(initializeResult);
if (FAILED(initializeResult) && initializeResult != RPC_E_CHANGED_MODE)
{
error = "CoInitializeEx failed: " + HResultToString(initializeResult);
return false;
}
CComPtr<IWICImagingFactory> factory;
HRESULT result = CoCreateInstance(
CLSID_WICImagingFactory,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&factory));
if (FAILED(result))
{
error = "Could not create WIC imaging factory: " + HResultToString(result);
if (shouldUninitialize)
CoUninitialize();
return false;
}
CComPtr<IWICStream> stream;
result = factory->CreateStream(&stream);
if (SUCCEEDED(result))
result = stream->InitializeFromFilename(outputPath.wstring().c_str(), GENERIC_WRITE);
if (FAILED(result))
{
error = "Could not open screenshot output file: " + HResultToString(result);
if (shouldUninitialize)
CoUninitialize();
return false;
}
CComPtr<IWICBitmapEncoder> encoder;
result = factory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &encoder);
if (SUCCEEDED(result))
result = encoder->Initialize(stream, WICBitmapEncoderNoCache);
if (FAILED(result))
{
error = "Could not initialize PNG encoder: " + HResultToString(result);
if (shouldUninitialize)
CoUninitialize();
return false;
}
CComPtr<IWICBitmapFrameEncode> frame;
CComPtr<IPropertyBag2> propertyBag;
result = encoder->CreateNewFrame(&frame, &propertyBag);
if (SUCCEEDED(result))
result = frame->Initialize(propertyBag);
if (SUCCEEDED(result))
result = frame->SetSize(width, height);
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppBGRA;
if (SUCCEEDED(result))
result = frame->SetPixelFormat(&pixelFormat);
if (SUCCEEDED(result) && pixelFormat != GUID_WICPixelFormat32bppBGRA)
{
error = "PNG encoder did not accept BGRA pixel format.";
result = E_FAIL;
}
const UINT stride = width * 4;
const UINT imageSize = stride * height;
if (SUCCEEDED(result))
result = frame->WritePixels(height, stride, imageSize, const_cast<BYTE*>(bgraPixels.data()));
if (SUCCEEDED(result))
result = frame->Commit();
if (SUCCEEDED(result))
result = encoder->Commit();
if (shouldUninitialize)
CoUninitialize();
if (FAILED(result))
{
error = "Could not write screenshot PNG: " + HResultToString(result);
std::error_code ignored;
std::filesystem::remove(outputPath, ignored);
return false;
}
return true;
}
}
void WritePngFileAsync(
const std::filesystem::path& outputPath,
unsigned width,
unsigned height,
std::vector<unsigned char> rgbaPixels)
{
std::thread(
[outputPath, width, height, pixels = std::move(rgbaPixels)]() mutable
{
for (std::size_t index = 0; index + 3 < pixels.size(); index += 4)
std::swap(pixels[index], pixels[index + 2]);
std::string error;
if (!WritePngFile(outputPath, width, height, pixels, error))
OutputDebugStringA(("Screenshot write failed: " + error + "\n").c_str());
else
OutputDebugStringA(("Screenshot written: " + outputPath.string() + "\n").c_str());
}).detach();
}

View File

@@ -1,12 +0,0 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
void WritePngFileAsync(
const std::filesystem::path& outputPath,
unsigned width,
unsigned height,
std::vector<unsigned char> rgbaPixels);

View File

@@ -1,41 +0,0 @@
#pragma once
#include "OpenGLRenderer.h"
#include "ShaderTypes.h"
#include <gl/gl.h>
#include <cstddef>
#include <string>
enum class RenderPassKind
{
LayerEffect
};
enum class RenderPassOutputTarget
{
Temporary,
LayerTemp,
Composite
};
struct RenderPassDescriptor
{
RenderPassKind kind = RenderPassKind::LayerEffect;
RenderPassOutputTarget outputTarget = RenderPassOutputTarget::Composite;
std::size_t passIndex = 0;
std::string passId;
std::string layerId;
std::string shaderId;
GLuint layerInputTexture = 0;
GLuint sourceTexture = 0;
GLuint sourceFramebuffer = 0;
GLuint destinationTexture = 0;
GLuint destinationFramebuffer = 0;
OpenGLRenderer::LayerProgram* layerProgram = nullptr;
OpenGLRenderer::LayerProgram::PassProgram* passProgram = nullptr;
const RuntimeRenderState* layerState = nullptr;
bool capturePreLayerHistory = false;
bool captureFeedbackWrite = false;
};

View File

@@ -1,202 +0,0 @@
#include "ShaderFeedbackBuffers.h"
#include <set>
namespace
{
void ConfigureFeedbackTexture(unsigned frameWidth, unsigned frameHeight)
{
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_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL);
}
}
bool ShaderFeedbackBuffers::EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error)
{
if (!EnsureZeroTexture())
{
error = "Failed to initialize shader feedback fallback texture.";
return false;
}
std::set<std::string> requiredLayerIds;
for (const RuntimeRenderState& state : layerStates)
{
if (!state.feedback.enabled)
continue;
requiredLayerIds.insert(state.layerId);
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
if (surfaceIt == mSurfacesByLayerId.end() ||
surfaceIt->second.width != frameWidth ||
surfaceIt->second.height != frameHeight)
{
Surface replacement;
if (!CreateSurface(replacement, frameWidth, frameHeight, error))
return false;
mSurfacesByLayerId[state.layerId] = std::move(replacement);
}
}
for (auto it = mSurfacesByLayerId.begin(); it != mSurfacesByLayerId.end();)
{
if (requiredLayerIds.find(it->first) == requiredLayerIds.end())
{
DestroySurface(it->second);
it = mSurfacesByLayerId.erase(it);
}
else
{
++it;
}
}
return true;
}
void ShaderFeedbackBuffers::DestroyResources()
{
for (auto& entry : mSurfacesByLayerId)
DestroySurface(entry.second);
mSurfacesByLayerId.clear();
if (mZeroTexture != 0)
{
glDeleteTextures(1, &mZeroTexture);
mZeroTexture = 0;
}
}
void ShaderFeedbackBuffers::ResetState()
{
for (auto& entry : mSurfacesByLayerId)
ClearSurfaceState(entry.second);
}
GLuint ShaderFeedbackBuffers::ResolveReadTexture(const RuntimeRenderState& state) const
{
if (!state.feedback.enabled)
return mZeroTexture;
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
if (surfaceIt == mSurfacesByLayerId.end() || !surfaceIt->second.hasData)
return mZeroTexture;
return surfaceIt->second.slots[surfaceIt->second.readIndex].texture != 0
? surfaceIt->second.slots[surfaceIt->second.readIndex].texture
: mZeroTexture;
}
bool ShaderFeedbackBuffers::FeedbackAvailable(const RuntimeRenderState& state) const
{
if (!state.feedback.enabled)
return false;
auto surfaceIt = mSurfacesByLayerId.find(state.layerId);
return surfaceIt != mSurfacesByLayerId.end() && surfaceIt->second.hasData;
}
void ShaderFeedbackBuffers::CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
{
auto surfaceIt = mSurfacesByLayerId.find(layerId);
if (surfaceIt == mSurfacesByLayerId.end())
return;
Surface& surface = surfaceIt->second;
const unsigned writeIndex = 1u - surface.readIndex;
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, surface.slots[writeIndex].framebuffer);
glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
surface.pendingWrite = true;
}
void ShaderFeedbackBuffers::FinalizeFrame()
{
for (auto& entry : mSurfacesByLayerId)
{
Surface& surface = entry.second;
if (!surface.pendingWrite)
continue;
surface.readIndex = 1u - surface.readIndex;
surface.hasData = true;
surface.pendingWrite = false;
}
}
bool ShaderFeedbackBuffers::EnsureZeroTexture()
{
if (mZeroTexture != 0)
return true;
glGenTextures(1, &mZeroTexture);
glBindTexture(GL_TEXTURE_2D, mZeroTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
const float zeroPixel[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, 1, 1, 0, GL_RGBA, GL_FLOAT, zeroPixel);
glBindTexture(GL_TEXTURE_2D, 0);
return mZeroTexture != 0;
}
bool ShaderFeedbackBuffers::CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error)
{
DestroySurface(surface);
surface.width = frameWidth;
surface.height = frameHeight;
for (Slot& slot : surface.slots)
{
glGenTextures(1, &slot.texture);
glBindTexture(GL_TEXTURE_2D, slot.texture);
ConfigureFeedbackTexture(frameWidth, frameHeight);
glGenFramebuffers(1, &slot.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
error = "Failed to initialize a shader feedback framebuffer.";
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
DestroySurface(surface);
return false;
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
ClearSurfaceState(surface);
return true;
}
void ShaderFeedbackBuffers::DestroySurface(Surface& surface)
{
for (Slot& slot : surface.slots)
{
if (slot.framebuffer != 0)
glDeleteFramebuffers(1, &slot.framebuffer);
if (slot.texture != 0)
glDeleteTextures(1, &slot.texture);
slot.framebuffer = 0;
slot.texture = 0;
}
surface.width = 0;
surface.height = 0;
surface.readIndex = 0;
surface.hasData = false;
surface.pendingWrite = false;
}
void ShaderFeedbackBuffers::ClearSurfaceState(Surface& surface)
{
surface.readIndex = 0;
surface.hasData = false;
surface.pendingWrite = false;
}

View File

@@ -1,46 +0,0 @@
#pragma once
#include "GLExtensions.h"
#include "ShaderTypes.h"
#include <map>
#include <string>
#include <vector>
class ShaderFeedbackBuffers
{
public:
struct Slot
{
GLuint texture = 0;
GLuint framebuffer = 0;
};
struct Surface
{
Slot slots[2];
unsigned width = 0;
unsigned height = 0;
unsigned readIndex = 0;
bool hasData = false;
bool pendingWrite = false;
};
bool EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned frameWidth, unsigned frameHeight, std::string& error);
void DestroyResources();
void ResetState();
GLuint ResolveReadTexture(const RuntimeRenderState& state) const;
bool FeedbackAvailable(const RuntimeRenderState& state) const;
void CaptureFeedbackFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
void FinalizeFrame();
private:
bool EnsureZeroTexture();
bool CreateSurface(Surface& surface, unsigned frameWidth, unsigned frameHeight, std::string& error);
void DestroySurface(Surface& surface);
void ClearSurfaceState(Surface& surface);
private:
std::map<std::string, Surface> mSurfacesByLayerId;
GLuint mZeroTexture = 0;
};

View File

@@ -1,261 +0,0 @@
#include "TemporalHistoryBuffers.h"
#include "GlRenderConstants.h"
#include "ShaderTypes.h"
#include <algorithm>
#include <sstream>
#include <set>
bool TemporalHistoryBuffers::ValidateTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, std::string& error) const
{
unsigned requiredUnits = kSourceHistoryTextureUnitBase;
for (const RuntimeRenderState& state : layerStates)
{
unsigned textTextureCount = 0;
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
if (definition.type == ShaderParameterType::Text)
++textTextureCount;
}
const unsigned totalShaderTextures = static_cast<unsigned>(state.textureAssets.size()) + textTextureCount;
const unsigned feedbackTextureCount = state.feedback.enabled ? 1u : 0u;
const unsigned layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + feedbackTextureCount + totalShaderTextures;
if (layerRequiredUnits > requiredUnits)
requiredUnits = layerRequiredUnits;
}
GLint maxTextureUnits = 0;
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
if (requiredUnits > availableUnits)
{
std::ostringstream message;
message << "The current history and shader texture asset configuration requires " << requiredUnits
<< " fragment texture units, but only " << maxTextureUnits << " are available.";
error = message.str();
return false;
}
return true;
}
bool TemporalHistoryBuffers::EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, unsigned frameWidth, unsigned frameHeight, std::string& error)
{
const bool sourceHistoryNeeded = std::any_of(layerStates.begin(), layerStates.end(),
[](const RuntimeRenderState& state) { return state.isTemporal && state.effectiveTemporalHistoryLength > 0; });
const unsigned sourceHistoryLength = sourceHistoryNeeded ? historyCap : 0;
if (sourceHistoryRing.effectiveLength != sourceHistoryLength)
{
if (!CreateRing(sourceHistoryRing, sourceHistoryLength, TemporalHistorySource::Source, frameWidth, frameHeight, error))
return false;
mNeedsReset = true;
}
std::set<std::string> requiredPreLayerIds;
for (const RuntimeRenderState& state : layerStates)
{
if (!state.isTemporal || state.temporalHistorySource != TemporalHistorySource::PreLayerInput)
continue;
requiredPreLayerIds.insert(state.layerId);
auto historyIt = preLayerHistoryByLayerId.find(state.layerId);
if (historyIt == preLayerHistoryByLayerId.end() || historyIt->second.effectiveLength != state.effectiveTemporalHistoryLength)
{
Ring replacement;
if (!CreateRing(replacement, state.effectiveTemporalHistoryLength, TemporalHistorySource::PreLayerInput, frameWidth, frameHeight, error))
return false;
preLayerHistoryByLayerId[state.layerId] = std::move(replacement);
mNeedsReset = true;
}
}
for (auto it = preLayerHistoryByLayerId.begin(); it != preLayerHistoryByLayerId.end();)
{
if (requiredPreLayerIds.find(it->first) == requiredPreLayerIds.end())
{
DestroyRing(it->second);
it = preLayerHistoryByLayerId.erase(it);
mNeedsReset = true;
}
else
{
++it;
}
}
if (mNeedsReset)
ResetState();
return true;
}
bool TemporalHistoryBuffers::CreateRing(Ring& ring, unsigned effectiveLength, TemporalHistorySource historySource, unsigned frameWidth, unsigned frameHeight, std::string& error)
{
DestroyRing(ring);
ring.effectiveLength = effectiveLength;
ring.historySource = historySource;
if (effectiveLength == 0)
return true;
ring.slots.resize(effectiveLength);
for (Slot& slot : ring.slots)
{
glGenTextures(1, &slot.texture);
glBindTexture(GL_TEXTURE_2D, slot.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_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, frameWidth, frameHeight, 0, GL_RGBA, GL_FLOAT, NULL);
glGenFramebuffers(1, &slot.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, slot.framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, slot.texture, 0);
const GLenum framebufferStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (framebufferStatus != GL_FRAMEBUFFER_COMPLETE)
{
error = "Failed to initialize a temporal history framebuffer.";
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
DestroyRing(ring);
return false;
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void TemporalHistoryBuffers::DestroyRing(Ring& ring)
{
for (Slot& slot : ring.slots)
{
if (slot.framebuffer != 0)
glDeleteFramebuffers(1, &slot.framebuffer);
if (slot.texture != 0)
glDeleteTextures(1, &slot.texture);
slot.framebuffer = 0;
slot.texture = 0;
}
ring.slots.clear();
ring.nextWriteIndex = 0;
ring.filledCount = 0;
ring.effectiveLength = 0;
ring.historySource = TemporalHistorySource::None;
}
void TemporalHistoryBuffers::DestroyResources()
{
DestroyRing(sourceHistoryRing);
for (auto& historyEntry : preLayerHistoryByLayerId)
DestroyRing(historyEntry.second);
preLayerHistoryByLayerId.clear();
mNeedsReset = true;
}
void TemporalHistoryBuffers::ResetState()
{
sourceHistoryRing.nextWriteIndex = 0;
sourceHistoryRing.filledCount = 0;
for (auto& historyEntry : preLayerHistoryByLayerId)
{
historyEntry.second.nextWriteIndex = 0;
historyEntry.second.filledCount = 0;
}
mNeedsReset = false;
}
void TemporalHistoryBuffers::PushFramebuffer(GLuint sourceFramebuffer, Ring& ring, unsigned frameWidth, unsigned frameHeight)
{
if (ring.effectiveLength == 0 || ring.slots.empty())
return;
Slot& targetSlot = ring.slots[ring.nextWriteIndex];
glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetSlot.framebuffer);
glBlitFramebuffer(0, 0, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
ring.nextWriteIndex = (ring.nextWriteIndex + 1) % ring.slots.size();
ring.filledCount = std::min<std::size_t>(ring.filledCount + 1, ring.slots.size());
}
void TemporalHistoryBuffers::PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
{
PushFramebuffer(sourceFramebuffer, sourceHistoryRing, frameWidth, frameHeight);
}
void TemporalHistoryBuffers::PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight)
{
auto historyIt = preLayerHistoryByLayerId.find(layerId);
if (historyIt != preLayerHistoryByLayerId.end())
PushFramebuffer(sourceFramebuffer, historyIt->second, frameWidth, frameHeight);
}
void TemporalHistoryBuffers::BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap)
{
for (unsigned index = 0; index < historyCap; ++index)
{
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
glBindTexture(GL_TEXTURE_2D, ResolveTexture(sourceHistoryRing, currentSourceTexture, index));
}
const GLuint temporalBase = kSourceHistoryTextureUnitBase + historyCap;
const Ring* temporalRing = nullptr;
auto it = preLayerHistoryByLayerId.find(state.layerId);
if (it != preLayerHistoryByLayerId.end())
temporalRing = &it->second;
for (unsigned index = 0; index < historyCap; ++index)
{
glActiveTexture(GL_TEXTURE0 + temporalBase + index);
glBindTexture(GL_TEXTURE_2D, temporalRing ? ResolveTexture(*temporalRing, currentSourceTexture, index) : currentSourceTexture);
}
glActiveTexture(GL_TEXTURE0);
}
std::vector<GLuint> TemporalHistoryBuffers::ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const
{
std::vector<GLuint> textures;
textures.reserve(historyCap);
for (unsigned index = 0; index < historyCap; ++index)
textures.push_back(ResolveTexture(sourceHistoryRing, fallbackTexture, index));
return textures;
}
std::vector<GLuint> TemporalHistoryBuffers::ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const
{
const Ring* temporalRing = nullptr;
auto it = preLayerHistoryByLayerId.find(state.layerId);
if (it != preLayerHistoryByLayerId.end())
temporalRing = &it->second;
std::vector<GLuint> textures;
textures.reserve(historyCap);
for (unsigned index = 0; index < historyCap; ++index)
textures.push_back(temporalRing ? ResolveTexture(*temporalRing, fallbackTexture, index) : fallbackTexture);
return textures;
}
GLuint TemporalHistoryBuffers::ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const
{
if (ring.filledCount == 0 || ring.slots.empty())
return fallbackTexture;
const std::size_t clampedOffset = std::min<std::size_t>(framesAgo, ring.filledCount - 1);
const std::size_t newestIndex = (ring.nextWriteIndex + ring.slots.size() - 1) % ring.slots.size();
const std::size_t slotIndex = (newestIndex + ring.slots.size() - clampedOffset) % ring.slots.size();
return ring.slots[slotIndex].texture != 0 ? ring.slots[slotIndex].texture : fallbackTexture;
}
unsigned TemporalHistoryBuffers::SourceAvailableCount() const
{
return static_cast<unsigned>(sourceHistoryRing.filledCount);
}
unsigned TemporalHistoryBuffers::AvailableCountForLayer(const std::string& layerId) const
{
auto it = preLayerHistoryByLayerId.find(layerId);
if (it == preLayerHistoryByLayerId.end())
return 0;
return static_cast<unsigned>(it->second.filledCount);
}

View File

@@ -1,53 +0,0 @@
#pragma once
#include "GLExtensions.h"
#include "ShaderTypes.h"
#include <windows.h>
#include <gl/gl.h>
#include <map>
#include <string>
#include <vector>
struct RuntimeRenderState;
class TemporalHistoryBuffers
{
public:
struct Slot
{
GLuint texture = 0;
GLuint framebuffer = 0;
};
struct Ring
{
std::vector<Slot> slots;
std::size_t nextWriteIndex = 0;
std::size_t filledCount = 0;
unsigned effectiveLength = 0;
TemporalHistorySource historySource = TemporalHistorySource::None;
};
bool ValidateTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, std::string& error) const;
bool EnsureResources(const std::vector<RuntimeRenderState>& layerStates, unsigned historyCap, unsigned frameWidth, unsigned frameHeight, std::string& error);
bool CreateRing(Ring& ring, unsigned effectiveLength, TemporalHistorySource historySource, unsigned frameWidth, unsigned frameHeight, std::string& error);
void DestroyRing(Ring& ring);
void DestroyResources();
void ResetState();
void PushFramebuffer(GLuint sourceFramebuffer, Ring& ring, unsigned frameWidth, unsigned frameHeight);
void PushSourceFramebuffer(GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
void PushPreLayerFramebuffer(const std::string& layerId, GLuint sourceFramebuffer, unsigned frameWidth, unsigned frameHeight);
void BindSamplers(const RuntimeRenderState& state, GLuint currentSourceTexture, unsigned historyCap);
std::vector<GLuint> ResolveSourceHistoryTextures(GLuint fallbackTexture, unsigned historyCap) const;
std::vector<GLuint> ResolveTemporalHistoryTextures(const RuntimeRenderState& state, GLuint fallbackTexture, unsigned historyCap) const;
GLuint ResolveTexture(const Ring& ring, GLuint fallbackTexture, std::size_t framesAgo) const;
unsigned SourceAvailableCount() const;
unsigned AvailableCountForLayer(const std::string& layerId) const;
private:
Ring sourceHistoryRing;
std::map<std::string, Ring> preLayerHistoryByLayerId;
bool mNeedsReset = true;
};

View File

@@ -1,212 +0,0 @@
/* -LICENSE-START-
** Copyright (c) 2012 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation (the
** "Software") to use, reproduce, display, distribute, sub-license, execute,
** and transmit the Software, and to prepare derivative works of the Software,
** and to permit third-parties to whom the Software is furnished to do so, in
** accordance with:
**
** (1) if the Software is obtained from Blackmagic Design, the End User License
** Agreement for the Software Development Kit ("EULA") available at
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
**
** (2) if the Software is obtained from any third party, such licensing terms
** as notified by that third party,
**
** and all subject to the following:
**
** (3) the copyright notices in the Software and this entire statement,
** including the above license grant, this restriction and the following
** disclaimer, must be included in all copies of the Software, in whole or in
** part, and all derivative works of the Software, unless such copies or
** derivative works are solely in the form of machine-executable object code
** generated by a source language processor.
**
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
**
** A copy of the Software is available free of charge at
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
**
** -LICENSE-END-
*/
//
// GLExtensions.cpp
// LoopThroughWithOpenGLCompositing
//
#include "GLExtensions.h"
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
PFNGLFENCESYNCPROC glFenceSync;
PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
PFNGLDELETESYNCPROC glDeleteSync;
PFNGLGENBUFFERSPROC glGenBuffers;
PFNGLDELETEBUFFERSPROC glDeleteBuffers;
PFNGLBINDBUFFERPROC glBindBuffer;
PFNGLBUFFERDATAPROC glBufferData;
PFNGLMAPBUFFERPROC glMapBuffer;
PFNGLUNMAPBUFFERPROC glUnmapBuffer;
PFNGLBUFFERSUBDATAPROC glBufferSubData;
PFNGLBINDBUFFERBASEPROC glBindBufferBase;
PFNGLACTIVETEXTUREPROC glActiveTexture;
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
PFNGLCREATESHADERPROC glCreateShader;
PFNGLDELETESHADERPROC glDeleteShader;
PFNGLDELETEPROGRAMPROC glDeleteProgram;
PFNGLSHADERSOURCEPROC glShaderSource;
PFNGLCOMPILESHADERPROC glCompileShader;
PFNGLGETSHADERIVPROC glGetShaderiv;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
PFNGLCREATEPROGRAMPROC glCreateProgram;
PFNGLATTACHSHADERPROC glAttachShader;
PFNGLLINKPROGRAMPROC glLinkProgram;
PFNGLGETPROGRAMIVPROC glGetProgramiv;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
PFNGLUSEPROGRAMPROC glUseProgram;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
PFNGLUNIFORM1IPROC glUniform1i;
PFNGLUNIFORM1FPROC glUniform1f;
PFNGLUNIFORM2FPROC glUniform2f;
PFNGLUNIFORM4FPROC glUniform4f;
bool ResolveGLExtensions()
{
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) wglGetProcAddress("glGenFramebuffers");
if (!glGenFramebuffers)
glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC) wglGetProcAddress("glGenFramebuffersEXT");
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) wglGetProcAddress("glGenRenderbuffers");
if (!glGenRenderbuffers)
glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC) wglGetProcAddress("glGenRenderbuffersEXT");
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) wglGetProcAddress("glBindRenderbuffer");
if (!glBindRenderbuffer)
glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC) wglGetProcAddress("glBindRenderbufferEXT");
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) wglGetProcAddress("glRenderbufferStorage");
if (!glRenderbufferStorage)
glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) wglGetProcAddress("glRenderbufferStorageEXT");
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) wglGetProcAddress("glDeleteFramebuffers");
if (!glDeleteFramebuffers)
glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC) wglGetProcAddress("glDeleteFramebuffersEXT");
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) wglGetProcAddress("glDeleteRenderbuffers");
if (!glDeleteRenderbuffers)
glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC) wglGetProcAddress("glDeleteRenderbuffersEXT");
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) wglGetProcAddress("glBindFramebuffer");
if (!glBindFramebuffer)
glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC) wglGetProcAddress("glBindFramebufferEXT");
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) wglGetProcAddress("glFramebufferTexture2D");
if (!glFramebufferTexture2D)
glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC) wglGetProcAddress("glFramebufferTexture2DEXT");
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) wglGetProcAddress("glFramebufferRenderbuffer");
if (!glFramebufferRenderbuffer)
glFramebufferRenderbuffer = (PFNGLFRAMEBUFFERRENDERBUFFERPROC) wglGetProcAddress("glFramebufferRenderbufferEXT");
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) wglGetProcAddress("glCheckFramebufferStatus");
if (!glCheckFramebufferStatus)
glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC) wglGetProcAddress("glCheckFramebufferStatusEXT");
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) wglGetProcAddress("glBlitFramebuffer");
if (!glBlitFramebuffer)
glBlitFramebuffer = (PFNGLBLITFRAMEBUFFERPROC) wglGetProcAddress("glBlitFramebufferEXT");
glFenceSync = (PFNGLFENCESYNCPROC) wglGetProcAddress("glFenceSync");
glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC) wglGetProcAddress("glClientWaitSync");
glDeleteSync = (PFNGLDELETESYNCPROC) wglGetProcAddress("glDeleteSync");
glGenBuffers = (PFNGLGENBUFFERSPROC) wglGetProcAddress("glGenBuffers");
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC) wglGetProcAddress("glDeleteBuffers");
glBindBuffer = (PFNGLBINDBUFFERPROC) wglGetProcAddress("glBindBuffer");
glBufferData = (PFNGLBUFFERDATAPROC) wglGetProcAddress("glBufferData");
glMapBuffer = (PFNGLMAPBUFFERPROC) wglGetProcAddress("glMapBuffer");
glUnmapBuffer = (PFNGLUNMAPBUFFERPROC) wglGetProcAddress("glUnmapBuffer");
glBufferSubData = (PFNGLBUFFERSUBDATAPROC) wglGetProcAddress("glBufferSubData");
glBindBufferBase = (PFNGLBINDBUFFERBASEPROC) wglGetProcAddress("glBindBufferBase");
glActiveTexture = (PFNGLACTIVETEXTUREPROC) wglGetProcAddress("glActiveTexture");
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC) wglGetProcAddress("glGenVertexArrays");
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC) wglGetProcAddress("glDeleteVertexArrays");
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC) wglGetProcAddress("glBindVertexArray");
glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
glDeleteShader = (PFNGLDELETESHADERPROC) wglGetProcAddress("glDeleteShader");
glDeleteProgram = (PFNGLDELETEPROGRAMPROC) wglGetProcAddress("glDeleteProgram");
glShaderSource = (PFNGLSHADERSOURCEPROC) wglGetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
glGetShaderiv = (PFNGLGETSHADERIVPROC) wglGetProcAddress("glGetShaderiv");
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC) wglGetProcAddress("glGetShaderInfoLog");
glCreateProgram = (PFNGLCREATEPROGRAMPROC) wglGetProcAddress("glCreateProgram");
glAttachShader = (PFNGLATTACHSHADERPROC) wglGetProcAddress("glAttachShader");
glLinkProgram = (PFNGLLINKPROGRAMPROC) wglGetProcAddress("glLinkProgram");
glGetProgramiv = (PFNGLGETPROGRAMIVPROC) wglGetProcAddress("glGetProgramiv");
glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC) wglGetProcAddress("glGetProgramInfoLog");
glUseProgram = (PFNGLUSEPROGRAMPROC) wglGetProcAddress("glUseProgram");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress("glGetUniformLocation");
glGetUniformBlockIndex = (PFNGLGETUNIFORMBLOCKINDEXPROC) wglGetProcAddress("glGetUniformBlockIndex");
glUniformBlockBinding = (PFNGLUNIFORMBLOCKBINDINGPROC) wglGetProcAddress("glUniformBlockBinding");
glUniform1i = (PFNGLUNIFORM1IPROC) wglGetProcAddress("glUniform1i");
glUniform1f = (PFNGLUNIFORM1FPROC) wglGetProcAddress("glUniform1f");
glUniform2f = (PFNGLUNIFORM2FPROC) wglGetProcAddress("glUniform2f");
glUniform4f = (PFNGLUNIFORM4FPROC) wglGetProcAddress("glUniform4f");
return glGenFramebuffers
&& glGenRenderbuffers
&& glBindRenderbuffer
&& glRenderbufferStorage
&& glDeleteFramebuffers
&& glDeleteRenderbuffers
&& glBindFramebuffer
&& glFramebufferTexture2D
&& glFramebufferRenderbuffer
&& glCheckFramebufferStatus
&& glBlitFramebuffer
&& glFenceSync
&& glClientWaitSync
&& glDeleteSync
&& glGenBuffers
&& glDeleteBuffers
&& glBindBuffer
&& glBufferData
&& glMapBuffer
&& glUnmapBuffer
&& glBufferSubData
&& glBindBufferBase
&& glActiveTexture
&& glGenVertexArrays
&& glDeleteVertexArrays
&& glBindVertexArray
&& glCreateShader
&& glDeleteShader
&& glDeleteProgram
&& glShaderSource
&& glCompileShader
&& glGetShaderiv
&& glGetShaderInfoLog
&& glCreateProgram
&& glAttachShader
&& glLinkProgram
&& glGetProgramiv
&& glGetProgramInfoLog
&& glUseProgram
&& glGetUniformLocation
&& glGetUniformBlockIndex
&& glUniformBlockBinding
&& glUniform1i
&& glUniform1f
&& glUniform2f
&& glUniform4f
;
}

View File

@@ -1,201 +0,0 @@
/* -LICENSE-START-
** Copyright (c) 2012 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation (the
** "Software") to use, reproduce, display, distribute, sub-license, execute,
** and transmit the Software, and to prepare derivative works of the Software,
** and to permit third-parties to whom the Software is furnished to do so, in
** accordance with:
**
** (1) if the Software is obtained from Blackmagic Design, the End User License
** Agreement for the Software Development Kit ("EULA") available at
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
**
** (2) if the Software is obtained from any third party, such licensing terms
** as notified by that third party,
**
** and all subject to the following:
**
** (3) the copyright notices in the Software and this entire statement,
** including the above license grant, this restriction and the following
** disclaimer, must be included in all copies of the Software, in whole or in
** part, and all derivative works of the Software, unless such copies or
** derivative works are solely in the form of machine-executable object code
** generated by a source language processor.
**
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
**
** A copy of the Software is available free of charge at
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
**
** -LICENSE-END-
*/
//
// GLExtensions.h
// LoopThroughWithOpenGLCompositing
//
#ifndef __GLEXTENSIONS_H__
#define __GLEXTENSIONS_H__
#include <windows.h>
#include <gl/gl.h>
#ifndef APIENTRYP
#define APIENTRYP APIENTRY *
#endif
#define GL_BGRA 0x80E1
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
#define GL_STREAM_DRAW 0x88E0
#define GL_STREAM_READ 0x88E1
#define GL_STREAM_COPY 0x88E2
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_UNIFORM_BUFFER 0x8A11
#define GL_RGBA8 0x8058
#define GL_RGBA16F 0x881A
#define GL_TEXTURE0 0x84C0
#define GL_ACTIVE_TEXTURE 0x84E0
#define GL_ARRAY_BUFFER 0x8892
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_COMPILE_STATUS 0x8B81
#define GL_LINK_STATUS 0x8B82
#define GL_INVALID_INDEX 0xFFFFFFFFu
#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
#define GL_RENDERBUFFER_EXT 0x8D41
#define GL_FRAMEBUFFER_EXT 0x8D40
#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5
#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0
#define GL_READ_FRAMEBUFFER 0x8CA8
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#define GL_RENDERBUFFER 0x8D41
#define GL_FRAMEBUFFER 0x8D40
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_DEPTH_COMPONENT24 0x81A6
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_DEPTH_ATTACHMENT_EXT 0x8D00
#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
#define GL_ALREADY_SIGNALED 0x911A
#define GL_TIMEOUT_EXPIRED 0x911B
#define GL_CONDITION_SATISFIED 0x911C
#define GL_WAIT_FAILED 0x911D
#define GL_READ_ONLY 0x88B8
typedef struct __GLsync *GLsync;
typedef unsigned __int64 GLuint64;
typedef ptrdiff_t GLintptr;
typedef ptrdiff_t GLsizeiptr;
typedef char GLchar;
typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer);
typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers);
typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers);
typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access);
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target);
typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader);
typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader);
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void);
typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type);
typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader);
typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params);
typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog);
typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name);
typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length);
typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program);
typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0);
typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0);
typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1);
typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar* uniformBlockName);
typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture);
typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer);
typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array);
typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint* arrays);
typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint* arrays);
typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags);
typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync);
typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer);
typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers);
typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers);
typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer);
typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers);
typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers);
typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target);
typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
extern PFNGLFENCESYNCPROC glFenceSync;
extern PFNGLCLIENTWAITSYNCPROC glClientWaitSync;
extern PFNGLDELETESYNCPROC glDeleteSync;
extern PFNGLGENBUFFERSPROC glGenBuffers;
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
extern PFNGLBINDBUFFERPROC glBindBuffer;
extern PFNGLBUFFERDATAPROC glBufferData;
extern PFNGLMAPBUFFERPROC glMapBuffer;
extern PFNGLUNMAPBUFFERPROC glUnmapBuffer;
extern PFNGLBUFFERSUBDATAPROC glBufferSubData;
extern PFNGLBINDBUFFERBASEPROC glBindBufferBase;
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
extern PFNGLCREATESHADERPROC glCreateShader;
extern PFNGLDELETESHADERPROC glDeleteShader;
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
extern PFNGLSHADERSOURCEPROC glShaderSource;
extern PFNGLCOMPILESHADERPROC glCompileShader;
extern PFNGLGETSHADERIVPROC glGetShaderiv;
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
extern PFNGLATTACHSHADERPROC glAttachShader;
extern PFNGLLINKPROGRAMPROC glLinkProgram;
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
extern PFNGLUSEPROGRAMPROC glUseProgram;
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
extern PFNGLGETUNIFORMBLOCKINDEXPROC glGetUniformBlockIndex;
extern PFNGLUNIFORMBLOCKBINDINGPROC glUniformBlockBinding;
extern PFNGLUNIFORM1IPROC glUniform1i;
extern PFNGLUNIFORM1FPROC glUniform1f;
extern PFNGLUNIFORM2FPROC glUniform2f;
extern PFNGLUNIFORM4FPROC glUniform4f;
bool ResolveGLExtensions();
#endif // __GLEXTENSIONS_H__

View File

@@ -1,9 +0,0 @@
#pragma once
#include <gl/gl.h>
constexpr GLuint kLayerInputTextureUnit = 0;
constexpr GLuint kDecodedVideoTextureUnit = 1;
constexpr GLuint kSourceHistoryTextureUnitBase = 2;
constexpr GLuint kPackedVideoTextureUnit = 2;
constexpr GLuint kGlobalParamsBindingPoint = 0;

View File

@@ -1,57 +0,0 @@
#pragma once
#include <gl/gl.h>
class ScopedGlShader
{
public:
explicit ScopedGlShader(GLuint shader = 0) : mShader(shader) {}
~ScopedGlShader() { reset(); }
ScopedGlShader(const ScopedGlShader&) = delete;
ScopedGlShader& operator=(const ScopedGlShader&) = delete;
GLuint get() const { return mShader; }
GLuint release()
{
GLuint shader = mShader;
mShader = 0;
return shader;
}
void reset(GLuint shader = 0)
{
if (mShader != 0)
glDeleteShader(mShader);
mShader = shader;
}
private:
GLuint mShader;
};
class ScopedGlProgram
{
public:
explicit ScopedGlProgram(GLuint program = 0) : mProgram(program) {}
~ScopedGlProgram() { reset(); }
ScopedGlProgram(const ScopedGlProgram&) = delete;
ScopedGlProgram& operator=(const ScopedGlProgram&) = delete;
GLuint get() const { return mProgram; }
GLuint release()
{
GLuint program = mProgram;
mProgram = 0;
return program;
}
void reset(GLuint program = 0)
{
if (mProgram != 0)
glDeleteProgram(mProgram);
mProgram = program;
}
private:
GLuint mProgram;
};

View File

@@ -1,268 +0,0 @@
#include "OpenGLRenderer.h"
#include "GlRenderConstants.h"
namespace
{
void ConfigureByteFrameTexture(unsigned width, unsigned height)
{
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, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
}
bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error)
{
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glDisable(GL_DEPTH_TEST);
glGenBuffers(1, &mTextureUploadBuffer);
glGenTextures(1, &mCaptureTexture);
glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
ConfigureByteFrameTexture(captureTextureWidth, inputFrameHeight);
glBindTexture(GL_TEXTURE_2D, 0);
glGenRenderbuffers(1, &mIdColorBuf);
glGenRenderbuffers(1, &mIdDepthBuf);
glGenVertexArrays(1, &mFullscreenVAO);
glGenBuffers(1, &mGlobalParamsUBO);
if (!mRenderTargets.Create(RenderTargetId::Decoded, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "decode", error))
return false;
if (!mRenderTargets.Create(RenderTargetId::LayerTemp, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "layer", error))
return false;
if (!mRenderTargets.Create(RenderTargetId::Composite, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "composite", error))
return false;
glBindFramebuffer(GL_FRAMEBUFFER, CompositeFramebuffer());
glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, inputFrameWidth, inputFrameHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
error = "Cannot initialize framebuffer.";
return false;
}
if (!mRenderTargets.Create(RenderTargetId::Output, outputFrameWidth, outputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "output", error))
return false;
if (!mRenderTargets.Create(RenderTargetId::OutputPack, outputPackTextureWidth, outputFrameHeight, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, "output pack", error))
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);
mResourcesInitialized = true;
return true;
}
void OpenGLRenderer::SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
{
mDecodeProgram = program;
mDecodeVertexShader = vertexShader;
mDecodeFragmentShader = fragmentShader;
mDecodePackedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uPackedVideoResolution") : -1;
mDecodeDecodedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uDecodedVideoResolution") : -1;
mDecodeInputPixelFormatLocation = program != 0 ? glGetUniformLocation(program, "uInputPixelFormat") : -1;
}
void OpenGLRenderer::SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
{
mOutputPackProgram = program;
mOutputPackVertexShader = vertexShader;
mOutputPackFragmentShader = fragmentShader;
mOutputPackResolutionLocation = program != 0 ? glGetUniformLocation(program, "uOutputVideoResolution") : -1;
mOutputPackActiveWordsLocation = program != 0 ? glGetUniformLocation(program, "uActiveV210Words") : -1;
mOutputPackFormatLocation = program != 0 ? glGetUniformLocation(program, "uOutputPackFormat") : -1;
}
bool OpenGLRenderer::ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error)
{
return mRenderTargets.ReserveTemporaryTargets(count, width, height, GL_RGBA16F, GL_RGBA, GL_FLOAT, error);
}
void OpenGLRenderer::ResizeView(int width, int height)
{
mViewWidth = width;
mViewHeight = height;
}
void OpenGLRenderer::PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight)
{
int destWidth = mViewWidth;
int destHeight = mViewHeight;
int destX = 0;
int destY = 0;
if (outputFrameWidth > 0 && outputFrameHeight > 0 && mViewWidth > 0 && mViewHeight > 0)
{
const double frameAspect = static_cast<double>(outputFrameWidth) / static_cast<double>(outputFrameHeight);
const double viewAspect = static_cast<double>(mViewWidth) / static_cast<double>(mViewHeight);
if (viewAspect > frameAspect)
{
destHeight = mViewHeight;
destWidth = static_cast<int>(destHeight * frameAspect + 0.5);
destX = (mViewWidth - destWidth) / 2;
}
else
{
destWidth = mViewWidth;
destHeight = static_cast<int>(destWidth / frameAspect + 0.5);
destY = (mViewHeight - destHeight) / 2;
}
}
glBindFramebuffer(GL_READ_FRAMEBUFFER, OutputFramebuffer());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDisable(GL_SCISSOR_TEST);
glViewport(0, 0, mViewWidth, mViewHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBlitFramebuffer(0, 0, outputFrameWidth, outputFrameHeight, destX, destY, destX + destWidth, destY + destHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
SwapBuffers(hdc);
}
void OpenGLRenderer::DestroyResources()
{
if (mFullscreenVAO != 0)
glDeleteVertexArrays(1, &mFullscreenVAO);
if (mGlobalParamsUBO != 0)
glDeleteBuffers(1, &mGlobalParamsUBO);
if (mIdColorBuf != 0)
glDeleteRenderbuffers(1, &mIdColorBuf);
if (mIdDepthBuf != 0)
glDeleteRenderbuffers(1, &mIdDepthBuf);
if (mCaptureTexture != 0)
glDeleteTextures(1, &mCaptureTexture);
if (mTextureUploadBuffer != 0)
glDeleteBuffers(1, &mTextureUploadBuffer);
mRenderTargets.Destroy();
mFullscreenVAO = 0;
mGlobalParamsUBO = 0;
mIdColorBuf = 0;
mIdDepthBuf = 0;
mCaptureTexture = 0;
mTextureUploadBuffer = 0;
mGlobalParamsUBOSize = 0;
mResourcesInitialized = false;
mTemporalHistory.DestroyResources();
mFeedbackBuffers.DestroyResources();
DestroyLayerPrograms();
DestroyDecodeShaderProgram();
DestroyOutputPackShaderProgram();
}
void OpenGLRenderer::DestroySingleLayerProgram(LayerProgram& layerProgram)
{
for (LayerProgram::PassProgram& passProgram : layerProgram.passes)
{
for (LayerProgram::TextureBinding& binding : passProgram.textureBindings)
{
if (binding.texture != 0)
{
glDeleteTextures(1, &binding.texture);
binding.texture = 0;
}
}
passProgram.textureBindings.clear();
for (LayerProgram::TextBinding& binding : passProgram.textBindings)
{
if (binding.texture != 0)
{
glDeleteTextures(1, &binding.texture);
binding.texture = 0;
}
}
passProgram.textBindings.clear();
if (passProgram.program != 0)
{
glDeleteProgram(passProgram.program);
passProgram.program = 0;
}
if (passProgram.fragmentShader != 0)
{
glDeleteShader(passProgram.fragmentShader);
passProgram.fragmentShader = 0;
}
if (passProgram.vertexShader != 0)
{
glDeleteShader(passProgram.vertexShader);
passProgram.vertexShader = 0;
}
}
layerProgram.passes.clear();
}
void OpenGLRenderer::DestroyLayerPrograms()
{
for (LayerProgram& layerProgram : mLayerPrograms)
DestroySingleLayerProgram(layerProgram);
mLayerPrograms.clear();
}
void OpenGLRenderer::DestroyDecodeShaderProgram()
{
if (mDecodeProgram != 0)
{
glDeleteProgram(mDecodeProgram);
mDecodeProgram = 0;
}
mDecodePackedResolutionLocation = -1;
mDecodeDecodedResolutionLocation = -1;
mDecodeInputPixelFormatLocation = -1;
if (mDecodeFragmentShader != 0)
{
glDeleteShader(mDecodeFragmentShader);
mDecodeFragmentShader = 0;
}
if (mDecodeVertexShader != 0)
{
glDeleteShader(mDecodeVertexShader);
mDecodeVertexShader = 0;
}
}
void OpenGLRenderer::DestroyOutputPackShaderProgram()
{
if (mOutputPackProgram != 0)
{
glDeleteProgram(mOutputPackProgram);
mOutputPackProgram = 0;
}
mOutputPackResolutionLocation = -1;
mOutputPackActiveWordsLocation = -1;
mOutputPackFormatLocation = -1;
if (mOutputPackFragmentShader != 0)
{
glDeleteShader(mOutputPackFragmentShader);
mOutputPackFragmentShader = 0;
}
if (mOutputPackVertexShader != 0)
{
glDeleteShader(mOutputPackVertexShader);
mOutputPackVertexShader = 0;
}
}

View File

@@ -1,131 +0,0 @@
#pragma once
#include "GLExtensions.h"
#include "RenderTargetPool.h"
#include "ShaderFeedbackBuffers.h"
#include "ShaderTypes.h"
#include "TemporalHistoryBuffers.h"
#include <windows.h>
#include <filesystem>
#include <gl/gl.h>
#include <string>
#include <vector>
class OpenGLRenderer
{
public:
struct LayerProgram
{
struct TextureBinding
{
std::string samplerName;
std::filesystem::path sourcePath;
GLuint texture = 0;
};
struct TextBinding
{
std::string parameterId;
std::string samplerName;
std::string fontId;
GLuint texture = 0;
std::string renderedText;
unsigned renderedWidth = 0;
unsigned renderedHeight = 0;
};
std::string layerId;
std::string shaderId;
struct PassProgram
{
std::string passId;
std::vector<std::string> inputNames;
std::string outputName;
GLuint shaderTextureBase = 0;
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
std::vector<TextureBinding> textureBindings;
std::vector<TextBinding> textBindings;
};
std::vector<PassProgram> passes;
};
GLuint CaptureTexture() const { return mCaptureTexture; }
GLuint DecodedTexture() const { return mRenderTargets.Texture(RenderTargetId::Decoded); }
GLuint LayerTempTexture() const { return mRenderTargets.Texture(RenderTargetId::LayerTemp); }
GLuint CompositeTexture() const { return mRenderTargets.Texture(RenderTargetId::Composite); }
GLuint OutputTexture() const { return mRenderTargets.Texture(RenderTargetId::Output); }
GLuint OutputPackTexture() const { return mRenderTargets.Texture(RenderTargetId::OutputPack); }
GLuint TextureUploadBuffer() const { return mTextureUploadBuffer; }
GLuint DecodeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Decoded); }
GLuint LayerTempFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::LayerTemp); }
GLuint CompositeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Composite); }
GLuint OutputFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Output); }
GLuint OutputPackFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::OutputPack); }
GLuint FullscreenVertexArray() const { return mFullscreenVAO; }
GLuint GlobalParamsUBO() const { return mGlobalParamsUBO; }
GLuint DecodeProgram() const { return mDecodeProgram; }
GLuint OutputPackProgram() const { return mOutputPackProgram; }
GLint DecodePackedResolutionLocation() const { return mDecodePackedResolutionLocation; }
GLint DecodeDecodedResolutionLocation() const { return mDecodeDecodedResolutionLocation; }
GLint DecodeInputPixelFormatLocation() const { return mDecodeInputPixelFormatLocation; }
GLint OutputPackResolutionLocation() const { return mOutputPackResolutionLocation; }
GLint OutputPackActiveWordsLocation() const { return mOutputPackActiveWordsLocation; }
GLint OutputPackFormatLocation() const { return mOutputPackFormatLocation; }
GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; }
void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; }
bool ResourcesInitialized() const { return mResourcesInitialized; }
void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); }
std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; }
const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; }
bool ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error);
const RenderTarget& TemporaryRenderTarget(std::size_t index) const { return mRenderTargets.TemporaryTarget(index); }
std::size_t TemporaryRenderTargetCount() const { return mRenderTargets.TemporaryTargetCount(); }
TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; }
const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; }
ShaderFeedbackBuffers& FeedbackBuffers() { return mFeedbackBuffers; }
const ShaderFeedbackBuffers& FeedbackBuffers() const { return mFeedbackBuffers; }
void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error);
void ResizeView(int width, int height);
void PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight);
void DestroyResources();
void DestroySingleLayerProgram(LayerProgram& layerProgram);
void DestroyLayerPrograms();
void DestroyDecodeShaderProgram();
void DestroyOutputPackShaderProgram();
private:
GLuint mCaptureTexture = 0;
GLuint mTextureUploadBuffer = 0;
GLuint mIdColorBuf = 0;
GLuint mIdDepthBuf = 0;
GLuint mFullscreenVAO = 0;
GLuint mGlobalParamsUBO = 0;
GLuint mDecodeProgram = 0;
GLuint mDecodeVertexShader = 0;
GLuint mDecodeFragmentShader = 0;
GLint mDecodePackedResolutionLocation = -1;
GLint mDecodeDecodedResolutionLocation = -1;
GLint mDecodeInputPixelFormatLocation = -1;
GLuint mOutputPackProgram = 0;
GLuint mOutputPackVertexShader = 0;
GLuint mOutputPackFragmentShader = 0;
GLint mOutputPackResolutionLocation = -1;
GLint mOutputPackActiveWordsLocation = -1;
GLint mOutputPackFormatLocation = -1;
GLsizeiptr mGlobalParamsUBOSize = 0;
bool mResourcesInitialized = false;
int mViewWidth = 0;
int mViewHeight = 0;
std::vector<LayerProgram> mLayerPrograms;
RenderTargetPool mRenderTargets;
TemporalHistoryBuffers mTemporalHistory;
ShaderFeedbackBuffers mFeedbackBuffers;
};

View File

@@ -1,136 +0,0 @@
#include "RenderTargetPool.h"
#include <cstddef>
namespace
{
void ConfigureRenderTargetTexture(
unsigned width,
unsigned height,
GLenum internalFormat,
GLenum pixelFormat,
GLenum pixelType)
{
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, internalFormat, width, height, 0, pixelFormat, pixelType, NULL);
}
}
bool RenderTargetPool::Create(
RenderTargetId id,
unsigned width,
unsigned height,
GLenum internalFormat,
GLenum pixelFormat,
GLenum pixelType,
const char* errorPrefix,
std::string& error)
{
RenderTarget& target = mTargets[TargetIndex(id)];
if (target.texture != 0 || target.framebuffer != 0)
{
error = std::string(errorPrefix) + " render target was already initialized.";
return false;
}
glGenTextures(1, &target.texture);
glBindTexture(GL_TEXTURE_2D, target.texture);
ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &target.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
error = std::string("Cannot initialize ") + errorPrefix + " framebuffer.";
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
target.width = width;
target.height = height;
target.internalFormat = internalFormat;
target.pixelFormat = pixelFormat;
target.pixelType = pixelType;
return true;
}
bool RenderTargetPool::ReserveTemporaryTargets(
std::size_t count,
unsigned width,
unsigned height,
GLenum internalFormat,
GLenum pixelFormat,
GLenum pixelType,
std::string& error)
{
if (mTemporaryTargets.size() == count)
return true;
DestroyTemporaryTargets();
mTemporaryTargets.resize(count);
for (std::size_t index = 0; index < mTemporaryTargets.size(); ++index)
{
RenderTarget& target = mTemporaryTargets[index];
glGenTextures(1, &target.texture);
glBindTexture(GL_TEXTURE_2D, target.texture);
ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &target.framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
error = "Cannot initialize temporary render target.";
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
target.width = width;
target.height = height;
target.internalFormat = internalFormat;
target.pixelFormat = pixelFormat;
target.pixelType = pixelType;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return true;
}
void RenderTargetPool::DestroyTemporaryTargets()
{
for (RenderTarget& target : mTemporaryTargets)
{
if (target.framebuffer != 0)
glDeleteFramebuffers(1, &target.framebuffer);
if (target.texture != 0)
glDeleteTextures(1, &target.texture);
}
mTemporaryTargets.clear();
}
void RenderTargetPool::Destroy()
{
for (RenderTarget& target : mTargets)
{
if (target.framebuffer != 0)
glDeleteFramebuffers(1, &target.framebuffer);
if (target.texture != 0)
glDeleteTextures(1, &target.texture);
target = RenderTarget();
}
DestroyTemporaryTargets();
}
const RenderTarget& RenderTargetPool::Target(RenderTargetId id) const
{
return mTargets[TargetIndex(id)];
}

View File

@@ -1,64 +0,0 @@
#pragma once
#include "GLExtensions.h"
#include <array>
#include <string>
#include <vector>
enum class RenderTargetId
{
Decoded,
LayerTemp,
Composite,
Output,
OutputPack,
Count
};
struct RenderTarget
{
GLuint texture = 0;
GLuint framebuffer = 0;
unsigned width = 0;
unsigned height = 0;
GLenum internalFormat = GL_RGBA8;
GLenum pixelFormat = GL_RGBA;
GLenum pixelType = GL_UNSIGNED_BYTE;
};
class RenderTargetPool
{
public:
bool Create(
RenderTargetId id,
unsigned width,
unsigned height,
GLenum internalFormat,
GLenum pixelFormat,
GLenum pixelType,
const char* errorPrefix,
std::string& error);
bool ReserveTemporaryTargets(
std::size_t count,
unsigned width,
unsigned height,
GLenum internalFormat,
GLenum pixelFormat,
GLenum pixelType,
std::string& error);
void DestroyTemporaryTargets();
void Destroy();
GLuint Texture(RenderTargetId id) const { return Target(id).texture; }
GLuint Framebuffer(RenderTargetId id) const { return Target(id).framebuffer; }
const RenderTarget& Target(RenderTargetId id) const;
const RenderTarget& TemporaryTarget(std::size_t index) const { return mTemporaryTargets[index]; }
std::size_t TemporaryTargetCount() const { return mTemporaryTargets.size(); }
private:
static std::size_t TargetIndex(RenderTargetId id) { return static_cast<std::size_t>(id); }
std::array<RenderTarget, static_cast<std::size_t>(RenderTargetId::Count)> mTargets;
std::vector<RenderTarget> mTemporaryTargets;
};

View File

@@ -1,172 +0,0 @@
#include "GlShaderSources.h"
const char* kFullscreenTriangleVertexShaderSource =
"#version 430 core\n"
"out vec2 vTexCoord;\n"
"void main()\n"
"{\n"
" vec2 positions[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n"
" vec2 texCoords[3] = vec2[3](vec2(0.0, 0.0), vec2(2.0, 0.0), vec2(0.0, 2.0));\n"
" gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);\n"
" vTexCoord = texCoords[gl_VertexID];\n"
"}\n";
const char* kDecodeFragmentShaderSource =
"#version 430 core\n"
"layout(binding = 2) uniform sampler2D uPackedVideoInput;\n"
"uniform vec2 uPackedVideoResolution;\n"
"uniform vec2 uDecodedVideoResolution;\n"
"uniform int uInputPixelFormat;\n"
"in vec2 vTexCoord;\n"
"layout(location = 0) out vec4 fragColor;\n"
"vec4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)\n"
"{\n"
" Y = (Y * 256.0 - 16.0) / 219.0;\n"
" Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;\n"
" Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;\n"
" return vec4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n"
"}\n"
"vec4 rec709YCbCr10_2rgba(float Y, float Cb, float Cr, float a)\n"
"{\n"
" Y = (Y - 64.0) / 876.0;\n"
" Cb = (Cb - 64.0) / 896.0 - 0.5;\n"
" Cr = (Cr - 64.0) / 896.0 - 0.5;\n"
" return vec4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n"
"}\n"
"uint loadV210Word(ivec2 coord)\n"
"{\n"
" vec4 b = round(texelFetch(uPackedVideoInput, coord, 0) * 255.0);\n"
" return uint(b.r) | (uint(b.g) << 8) | (uint(b.b) << 16) | (uint(b.a) << 24);\n"
"}\n"
"float v210Component(uint word, int index)\n"
"{\n"
" return float((word >> uint(index * 10)) & 1023u);\n"
"}\n"
"vec4 decodeUyvy8(ivec2 outputCoord, ivec2 packedSize)\n"
"{\n"
" ivec2 packedCoord = ivec2(clamp(outputCoord.x / 2, 0, packedSize.x - 1), clamp(outputCoord.y, 0, packedSize.y - 1));\n"
" vec4 macroPixel = texelFetch(uPackedVideoInput, packedCoord, 0);\n"
" float ySample = (outputCoord.x & 1) != 0 ? macroPixel.a : macroPixel.g;\n"
" return rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n"
"}\n"
"vec4 decodeV210(ivec2 outputCoord, ivec2 packedSize)\n"
"{\n"
" int group = outputCoord.x / 6;\n"
" int pixel = outputCoord.x - group * 6;\n"
" int wordBase = group * 4;\n"
" ivec2 rowBase = ivec2(wordBase, clamp(outputCoord.y, 0, packedSize.y - 1));\n"
" uint w0 = loadV210Word(ivec2(min(rowBase.x + 0, packedSize.x - 1), rowBase.y));\n"
" uint w1 = loadV210Word(ivec2(min(rowBase.x + 1, packedSize.x - 1), rowBase.y));\n"
" uint w2 = loadV210Word(ivec2(min(rowBase.x + 2, packedSize.x - 1), rowBase.y));\n"
" uint w3 = loadV210Word(ivec2(min(rowBase.x + 3, packedSize.x - 1), rowBase.y));\n"
" float y0 = v210Component(w0, 1);\n"
" float y1 = v210Component(w1, 0);\n"
" float y2 = v210Component(w1, 2);\n"
" float y3 = v210Component(w2, 1);\n"
" float y4 = v210Component(w3, 0);\n"
" float y5 = v210Component(w3, 2);\n"
" float cb0 = v210Component(w0, 0);\n"
" float cr0 = v210Component(w0, 2);\n"
" float cb2 = v210Component(w1, 1);\n"
" float cr2 = v210Component(w2, 0);\n"
" float cb4 = v210Component(w2, 2);\n"
" float cr4 = v210Component(w3, 1);\n"
" float ySample = pixel == 0 ? y0 : pixel == 1 ? y1 : pixel == 2 ? y2 : pixel == 3 ? y3 : pixel == 4 ? y4 : y5;\n"
" float cbSample = pixel < 2 ? cb0 : pixel < 4 ? cb2 : cb4;\n"
" float crSample = pixel < 2 ? cr0 : pixel < 4 ? cr2 : cr4;\n"
" return rec709YCbCr10_2rgba(ySample, cbSample, crSample, 1.0);\n"
"}\n"
"void main()\n"
"{\n"
" vec2 correctedUv = vec2(vTexCoord.x, 1.0 - vTexCoord.y);\n"
" ivec2 decodedSize = ivec2(max(uDecodedVideoResolution, vec2(1.0, 1.0)));\n"
" ivec2 outputCoord = clamp(ivec2(correctedUv * vec2(decodedSize)), ivec2(0, 0), decodedSize - ivec2(1, 1));\n"
" ivec2 packedSize = ivec2(max(uPackedVideoResolution, vec2(1.0, 1.0)));\n"
" fragColor = uInputPixelFormat == 1 ? decodeV210(outputCoord, packedSize) : decodeUyvy8(outputCoord, packedSize);\n"
"}\n";
const char* kOutputPackFragmentShaderSource =
"#version 430 core\n"
"layout(binding = 0) uniform sampler2D uOutputRgb;\n"
"uniform vec2 uOutputVideoResolution;\n"
"uniform float uActiveV210Words;\n"
"uniform int uOutputPackFormat;\n"
"in vec2 vTexCoord;\n"
"layout(location = 0) out vec4 fragColor;\n"
"vec4 rgbaAt(int x, int y)\n"
"{\n"
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0), vec4(0.0), vec4(1.0));\n"
"}\n"
"vec3 rgbAt(int x, int y)\n"
"{\n"
" return rgbaAt(x, y).rgb;\n"
"}\n"
"vec3 rgbToLegalYcbcr10(vec3 rgb)\n"
"{\n"
" float y = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\n"
" float cb = (rgb.b - y) / 1.8556 + 0.5;\n"
" float cr = (rgb.r - y) / 1.5748 + 0.5;\n"
" return vec3(clamp(round(64.0 + y * 876.0), 64.0, 940.0), clamp(round(64.0 + cb * 896.0), 64.0, 960.0), clamp(round(64.0 + cr * 896.0), 64.0, 960.0));\n"
"}\n"
"uint makeWord(float a, float b, float c)\n"
"{\n"
" return (uint(a) & 1023u) | ((uint(b) & 1023u) << 10) | ((uint(c) & 1023u) << 20);\n"
"}\n"
"vec4 wordToBytes(uint word)\n"
"{\n"
" return vec4(float(word & 255u), float((word >> 8) & 255u), float((word >> 16) & 255u), float((word >> 24) & 255u)) / 255.0;\n"
"}\n"
"vec4 bigEndianWordToBytes(uint word)\n"
"{\n"
" return vec4(float((word >> 24) & 255u), float((word >> 16) & 255u), float((word >> 8) & 255u), float(word & 255u)) / 255.0;\n"
"}\n"
"vec4 packAy10Word(ivec2 outCoord)\n"
"{\n"
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
" if (outCoord.x >= size.x)\n"
" return vec4(0.0);\n"
" int pixelBase = (outCoord.x / 2) * 2;\n"
" int y = outCoord.y;\n"
" vec4 rgba0 = rgbaAt(pixelBase + 0, y);\n"
" vec4 rgba1 = rgbaAt(pixelBase + 1, y);\n"
" vec3 c0 = rgbToLegalYcbcr10(rgba0.rgb);\n"
" vec3 c1 = rgbToLegalYcbcr10(rgba1.rgb);\n"
" float chroma = (outCoord.x & 1) == 0 ? round((c0.y + c1.y) * 0.5) : round((c0.z + c1.z) * 0.5);\n"
" float alpha = round(clamp(((outCoord.x & 1) == 0 ? rgba0.a : rgba1.a), 0.0, 1.0) * 1023.0);\n"
" float luma = (outCoord.x & 1) == 0 ? c0.x : c1.x;\n"
" uint word = ((uint(luma) & 1023u) << 22) | ((uint(chroma) & 1023u) << 12) | ((uint(alpha) & 1023u) << 2);\n"
" return bigEndianWordToBytes(word);\n"
"}\n"
"void main()\n"
"{\n"
" ivec2 outCoord = ivec2(gl_FragCoord.xy);\n"
" if (uOutputPackFormat == 2)\n"
" {\n"
" fragColor = packAy10Word(outCoord);\n"
" return;\n"
" }\n"
" if (float(outCoord.x) >= uActiveV210Words)\n"
" {\n"
" fragColor = vec4(0.0);\n"
" return;\n"
" }\n"
" int group = outCoord.x / 4;\n"
" int wordIndex = outCoord.x - group * 4;\n"
" int pixelBase = group * 6;\n"
" int y = outCoord.y;\n"
" vec3 c0 = rgbToLegalYcbcr10(rgbAt(pixelBase + 0, y));\n"
" vec3 c1 = rgbToLegalYcbcr10(rgbAt(pixelBase + 1, y));\n"
" vec3 c2 = rgbToLegalYcbcr10(rgbAt(pixelBase + 2, y));\n"
" vec3 c3 = rgbToLegalYcbcr10(rgbAt(pixelBase + 3, y));\n"
" vec3 c4 = rgbToLegalYcbcr10(rgbAt(pixelBase + 4, y));\n"
" vec3 c5 = rgbToLegalYcbcr10(rgbAt(pixelBase + 5, y));\n"
" float cb0 = round((c0.y + c1.y) * 0.5);\n"
" float cr0 = round((c0.z + c1.z) * 0.5);\n"
" float cb2 = round((c2.y + c3.y) * 0.5);\n"
" float cr2 = round((c2.z + c3.z) * 0.5);\n"
" float cb4 = round((c4.y + c5.y) * 0.5);\n"
" float cr4 = round((c4.z + c5.z) * 0.5);\n"
" uint word = wordIndex == 0 ? makeWord(cb0, c0.x, cr0) : wordIndex == 1 ? makeWord(c1.x, cb2, c2.x) : wordIndex == 2 ? makeWord(cr2, c3.x, cb4) : makeWord(c4.x, cr4, c5.x);\n"
" fragColor = wordToBytes(word);\n"
"}\n";

View File

@@ -1,5 +0,0 @@
#pragma once
extern const char* kFullscreenTriangleVertexShaderSource;
extern const char* kDecodeFragmentShaderSource;
extern const char* kOutputPackFragmentShaderSource;

View File

@@ -1,104 +0,0 @@
#include "GlobalParamsBuffer.h"
#include "GlRenderConstants.h"
#include "Std140Buffer.h"
#include <vector>
GlobalParamsBuffer::GlobalParamsBuffer(OpenGLRenderer& renderer) :
mRenderer(renderer)
{
}
bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
{
std::vector<unsigned char>& buffer = mScratchBuffer;
buffer.clear();
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.utcTimeSeconds));
AppendStd140Float(buffer, static_cast<float>(state.utcOffsetSeconds));
AppendStd140Float(buffer, static_cast<float>(state.startupRandom));
AppendStd140Float(buffer, static_cast<float>(state.frameCount));
AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
AppendStd140Float(buffer, static_cast<float>(state.bypass));
const unsigned effectiveSourceHistoryLength = availableSourceHistoryLength < state.effectiveTemporalHistoryLength
? availableSourceHistoryLength
: state.effectiveTemporalHistoryLength;
const unsigned effectiveTemporalHistoryLength = (state.temporalHistorySource == TemporalHistorySource::PreLayerInput)
? (availableTemporalHistoryLength < state.effectiveTemporalHistoryLength ? availableTemporalHistoryLength : state.effectiveTemporalHistoryLength)
: 0u;
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
AppendStd140Int(buffer, feedbackAvailable ? 1 : 0);
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;
}
case ShaderParameterType::Text:
break;
case ShaderParameterType::Trigger:
AppendStd140Int(buffer, value.numberValues.empty() ? 0 : static_cast<int>(value.numberValues[0]));
AppendStd140Float(buffer, value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : -1000000.0f);
break;
}
}
buffer.resize(AlignStd140(buffer.size(), 16), 0);
glBindBuffer(GL_UNIFORM_BUFFER, mRenderer.GlobalParamsUBO());
if (mRenderer.GlobalParamsUBOSize() != static_cast<GLsizeiptr>(buffer.size()))
{
glBufferData(GL_UNIFORM_BUFFER, static_cast<GLsizeiptr>(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
mRenderer.SetGlobalParamsUBOSize(static_cast<GLsizeiptr>(buffer.size()));
}
else
{
glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast<GLsizeiptr>(buffer.size()), buffer.data());
}
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mRenderer.GlobalParamsUBO());
glBindBuffer(GL_UNIFORM_BUFFER, 0);
return true;
}

View File

@@ -1,18 +0,0 @@
#pragma once
#include "OpenGLRenderer.h"
#include "ShaderTypes.h"
#include <vector>
class GlobalParamsBuffer
{
public:
explicit GlobalParamsBuffer(OpenGLRenderer& renderer);
bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
private:
OpenGLRenderer& mRenderer;
std::vector<unsigned char> mScratchBuffer;
};

View File

@@ -1,200 +0,0 @@
#include "OpenGLShaderPrograms.h"
#include <cstring>
#include <string>
#include <vector>
namespace
{
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{
if (!errorMessage || errorMessageSize <= 0)
return;
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
}
std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::LayerProgram>& layerPrograms)
{
// Only one layer renders at a time, so the pool needs to cover the widest
// layer, not the sum of every intermediate pass in the stack.
std::size_t requiredTargets = 0;
for (const OpenGLRenderer::LayerProgram& layerProgram : layerPrograms)
{
const std::size_t internalPasses = layerProgram.passes.size() > 0 ? layerProgram.passes.size() - 1 : 0;
if (internalPasses > requiredTargets)
requiredTargets = internalPasses;
}
return requiredTargets;
}
}
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider) :
mRenderer(renderer),
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mGlobalParamsBuffer(renderer),
mCompiler(renderer, runtimeSnapshotProvider, mTextureBindings)
{
}
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
const RuntimeRenderStateSnapshot renderSnapshot =
mRuntimeSnapshotProvider.PublishRenderStateSnapshot(inputFrameWidth, inputFrameHeight);
const std::vector<RuntimeRenderState>& layerStates = renderSnapshot.states;
std::string temporalError;
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (!mRenderer.TemporalHistory().EnsureResources(layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (mRenderer.ResourcesInitialized() &&
!mRenderer.FeedbackBuffers().EnsureResources(layerStates, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
// Initial startup still compiles synchronously; auto-reload uses the build
// queue so Slang/file work stays off the playback path.
std::vector<LayerProgram> newPrograms;
newPrograms.reserve(layerStates.size());
for (const RuntimeRenderState& state : layerStates)
{
LayerProgram layerProgram;
if (!mCompiler.CompileLayerProgram(state, layerProgram, errorMessageSize, errorMessage))
{
for (LayerProgram& program : newPrograms)
DestroySingleLayerProgram(program);
return false;
}
newPrograms.push_back(layerProgram);
}
std::string targetError;
if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
{
for (LayerProgram& program : newPrograms)
DestroySingleLayerProgram(program);
CopyErrorMessage(targetError, errorMessageSize, errorMessage);
return false;
}
DestroyLayerPrograms();
mRenderer.ReplaceLayerPrograms(newPrograms);
mCommittedLayerStates = renderSnapshot.states;
return true;
}
bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
if (!preparedBuild.succeeded)
{
CopyErrorMessage(preparedBuild.message, errorMessageSize, errorMessage);
return false;
}
std::string temporalError;
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.renderSnapshot.states, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
if (mRenderer.ResourcesInitialized() &&
!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
}
// The prepared build already contains GLSL text for each pass. This commit
// step performs the short GL work on the render thread.
std::vector<LayerProgram> newPrograms;
newPrograms.reserve(preparedBuild.layers.size());
for (const PreparedLayerShader& preparedLayer : preparedBuild.layers)
{
LayerProgram layerProgram;
if (!mCompiler.CompilePreparedLayerProgram(preparedLayer.state, preparedLayer.passes, layerProgram, errorMessageSize, errorMessage))
{
for (LayerProgram& program : newPrograms)
DestroySingleLayerProgram(program);
return false;
}
newPrograms.push_back(layerProgram);
}
std::string targetError;
if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
{
for (LayerProgram& program : newPrograms)
DestroySingleLayerProgram(program);
CopyErrorMessage(targetError, errorMessageSize, errorMessage);
return false;
}
DestroyLayerPrograms();
mRenderer.ReplaceLayerPrograms(newPrograms);
mCommittedLayerStates = preparedBuild.renderSnapshot.states;
return true;
}
bool OpenGLShaderPrograms::CompileDecodeShader(int errorMessageSize, char* errorMessage)
{
return mCompiler.CompileDecodeShader(errorMessageSize, errorMessage);
}
bool OpenGLShaderPrograms::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
{
return mCompiler.CompileOutputPackShader(errorMessageSize, errorMessage);
}
void OpenGLShaderPrograms::DestroySingleLayerProgram(LayerProgram& layerProgram)
{
mRenderer.DestroySingleLayerProgram(layerProgram);
}
void OpenGLShaderPrograms::DestroyLayerPrograms()
{
mRenderer.DestroyLayerPrograms();
}
void OpenGLShaderPrograms::DestroyDecodeShaderProgram()
{
mRenderer.DestroyDecodeShaderProgram();
}
void OpenGLShaderPrograms::ResetTemporalHistoryState()
{
mRenderer.TemporalHistory().ResetState();
}
void OpenGLShaderPrograms::ResetShaderFeedbackState()
{
mRenderer.FeedbackBuffers().ResetState();
}
bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
{
return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
}
bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
{
return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
}

View File

@@ -1,40 +0,0 @@
#pragma once
#include "GlobalParamsBuffer.h"
#include "OpenGLRenderer.h"
#include "RuntimeSnapshotProvider.h"
#include "ShaderBuildQueue.h"
#include "ShaderTypes.h"
#include "ShaderProgramCompiler.h"
#include "ShaderTextureBindings.h"
#include <string>
class OpenGLShaderPrograms
{
public:
using LayerProgram = OpenGLRenderer::LayerProgram;
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider);
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
void DestroyLayerPrograms();
void DestroySingleLayerProgram(LayerProgram& layerProgram);
void DestroyDecodeShaderProgram();
void ResetTemporalHistoryState();
void ResetShaderFeedbackState();
const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; }
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable);
private:
OpenGLRenderer& mRenderer;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
ShaderTextureBindings mTextureBindings;
GlobalParamsBuffer mGlobalParamsBuffer;
ShaderProgramCompiler mCompiler;
std::vector<RuntimeRenderState> mCommittedLayerStates;
};

View File

@@ -1,166 +0,0 @@
#include "ShaderBuildQueue.h"
#include "RuntimeEventDispatcher.h"
#include <chrono>
#include <utility>
namespace
{
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
}
ShaderBuildQueue::ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher) :
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mRuntimeEventDispatcher(runtimeEventDispatcher),
mWorkerThread([this]() { WorkerLoop(); })
{
}
ShaderBuildQueue::~ShaderBuildQueue()
{
Stop();
}
void ShaderBuildQueue::RequestBuild(unsigned outputWidth, unsigned outputHeight)
{
{
std::lock_guard<std::mutex> lock(mMutex);
mHasRequest = true;
++mRequestedGeneration;
mRequestedOutputWidth = outputWidth;
mRequestedOutputHeight = outputHeight;
mHasReadyBuild = false;
}
mCondition.notify_one();
}
bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasReadyBuild)
return false;
build = std::move(mReadyBuild);
mReadyBuild = PreparedShaderBuild();
mHasReadyBuild = false;
return true;
}
bool ShaderBuildQueue::TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasReadyBuild || mReadyBuild.generation != expectedGeneration)
return false;
build = std::move(mReadyBuild);
mReadyBuild = PreparedShaderBuild();
mHasReadyBuild = false;
return true;
}
void ShaderBuildQueue::Stop()
{
{
std::lock_guard<std::mutex> lock(mMutex);
if (mStopping)
return;
mStopping = true;
}
mCondition.notify_one();
if (mWorkerThread.joinable())
mWorkerThread.join();
}
void ShaderBuildQueue::WorkerLoop()
{
for (;;)
{
uint64_t generation = 0;
unsigned outputWidth = 0;
unsigned outputHeight = 0;
{
std::unique_lock<std::mutex> lock(mMutex);
mCondition.wait(lock, [this]() { return mStopping || mHasRequest; });
if (mStopping)
return;
generation = mRequestedGeneration;
outputWidth = mRequestedOutputWidth;
outputHeight = mRequestedOutputHeight;
mHasRequest = false;
}
for (;;)
{
std::unique_lock<std::mutex> lock(mMutex);
if (mCondition.wait_for(lock, kShaderBuildDebounce, [this, generation]() {
return mStopping || (mHasRequest && mRequestedGeneration != generation);
}))
{
if (mStopping)
return;
generation = mRequestedGeneration;
outputWidth = mRequestedOutputWidth;
outputHeight = mRequestedOutputHeight;
mHasRequest = false;
continue;
}
break;
}
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
bool shouldPublish = false;
{
std::lock_guard<std::mutex> lock(mMutex);
if (mStopping)
return;
if (generation != mRequestedGeneration)
continue;
mReadyBuild = build;
mHasReadyBuild = true;
shouldPublish = true;
}
if (shouldPublish)
PublishBuildLifecycleEvent(build, outputWidth, outputHeight);
}
}
PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight)
{
PreparedShaderBuild build;
build.generation = generation;
build.renderSnapshot = mRuntimeSnapshotProvider.PublishRenderStateSnapshot(outputWidth, outputHeight);
build.layers.reserve(build.renderSnapshot.states.size());
for (const RuntimeRenderState& state : build.renderSnapshot.states)
{
PreparedLayerShader layer;
layer.state = state;
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, layer.passes, build.message))
{
build.succeeded = false;
return build;
}
build.layers.push_back(std::move(layer));
}
build.succeeded = true;
build.message = "Shader layers prepared successfully.";
return build;
}
void ShaderBuildQueue::PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const
{
ShaderBuildEvent event;
event.phase = build.succeeded ? RuntimeEventShaderBuildPhase::Prepared : RuntimeEventShaderBuildPhase::Failed;
event.generation = build.generation;
event.inputWidth = outputWidth;
event.inputHeight = outputHeight;
event.succeeded = build.succeeded;
event.message = build.message;
mRuntimeEventDispatcher.PublishPayload(event, "ShaderBuildQueue");
}

View File

@@ -1,61 +0,0 @@
#pragma once
#include "RuntimeSnapshotProvider.h"
#include "ShaderTypes.h"
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
class RuntimeEventDispatcher;
struct PreparedLayerShader
{
RuntimeRenderState state;
std::vector<ShaderPassBuildSource> passes;
};
struct PreparedShaderBuild
{
uint64_t generation = 0;
bool succeeded = false;
std::string message;
RuntimeRenderStateSnapshot renderSnapshot;
std::vector<PreparedLayerShader> layers;
};
class ShaderBuildQueue
{
public:
ShaderBuildQueue(RuntimeSnapshotProvider& runtimeSnapshotProvider, RuntimeEventDispatcher& runtimeEventDispatcher);
~ShaderBuildQueue();
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
ShaderBuildQueue& operator=(const ShaderBuildQueue&) = delete;
void RequestBuild(unsigned outputWidth, unsigned outputHeight);
bool TryConsumeReadyBuild(PreparedShaderBuild& build);
bool TryConsumeReadyBuild(uint64_t expectedGeneration, PreparedShaderBuild& build);
void Stop();
private:
void WorkerLoop();
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
void PublishBuildLifecycleEvent(const PreparedShaderBuild& build, unsigned outputWidth, unsigned outputHeight) const;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
RuntimeEventDispatcher& mRuntimeEventDispatcher;
std::thread mWorkerThread;
std::mutex mMutex;
std::condition_variable mCondition;
bool mStopping = false;
bool mHasRequest = false;
uint64_t mRequestedGeneration = 0;
unsigned mRequestedOutputWidth = 0;
unsigned mRequestedOutputHeight = 0;
bool mHasReadyBuild = false;
PreparedShaderBuild mReadyBuild;
};

View File

@@ -1,233 +0,0 @@
#include "ShaderProgramCompiler.h"
#include "GlRenderConstants.h"
#include "GlScopedObjects.h"
#include "GlShaderSources.h"
#include <cstring>
#include <utility>
#include <vector>
namespace
{
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{
if (!errorMessage || errorMessageSize <= 0)
return;
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
}
}
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings) :
mRenderer(renderer),
mRuntimeSnapshotProvider(runtimeSnapshotProvider),
mTextureBindings(textureBindings)
{
}
bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
{
std::vector<ShaderPassBuildSource> passSources;
std::string loadError;
if (!mRuntimeSnapshotProvider.BuildLayerPassFragmentShaderSources(state.layerId, passSources, loadError))
{
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
return false;
}
return CompilePreparedLayerProgram(state, passSources, layerProgram, errorMessageSize, errorMessage);
}
bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
std::string loadError;
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
layerProgram.layerId = state.layerId;
layerProgram.shaderId = state.shaderId;
layerProgram.passes.clear();
for (const auto& passSource : passSources)
{
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
const char* fragmentSource = passSource.fragmentShaderSource.c_str();
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
glCompileShader(newVertexShader.get());
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
mRenderer.DestroySingleLayerProgram(layerProgram);
return false;
}
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
glCompileShader(newFragmentShader.get());
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
mRenderer.DestroySingleLayerProgram(layerProgram);
return false;
}
ScopedGlProgram newProgram(glCreateProgram());
glAttachShader(newProgram.get(), newVertexShader.get());
glAttachShader(newProgram.get(), newFragmentShader.get());
glLinkProgram(newProgram.get());
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
mRenderer.DestroySingleLayerProgram(layerProgram);
return false;
}
std::vector<LayerProgram::TextureBinding> textureBindings;
for (const ShaderTextureAsset& textureAsset : state.textureAssets)
{
LayerProgram::TextureBinding textureBinding;
textureBinding.samplerName = textureAsset.id;
textureBinding.sourcePath = textureAsset.path;
if (!mTextureBindings.LoadTextureAsset(textureAsset, textureBinding.texture, loadError))
{
for (LayerProgram::TextureBinding& loadedTexture : textureBindings)
{
if (loadedTexture.texture != 0)
glDeleteTextures(1, &loadedTexture.texture);
}
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
mRenderer.DestroySingleLayerProgram(layerProgram);
return false;
}
textureBindings.push_back(textureBinding);
}
std::vector<LayerProgram::TextBinding> textBindings;
mTextureBindings.CreateTextBindings(state, textBindings);
PassProgram passProgram;
passProgram.passId = passSource.passId;
passProgram.inputNames = passSource.inputNames;
passProgram.outputName = passSource.outputName;
passProgram.shaderTextureBase = mTextureBindings.ResolveShaderTextureBase(state, mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames());
passProgram.textureBindings.swap(textureBindings);
passProgram.textBindings.swap(textBindings);
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram.get(), "GlobalParams");
if (globalParamsIndex != GL_INVALID_INDEX)
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
glUseProgram(newProgram.get());
mTextureBindings.AssignLayerSamplerUniforms(newProgram.get(), state, passProgram, historyCap);
glUseProgram(0);
passProgram.program = newProgram.release();
passProgram.vertexShader = newVertexShader.release();
passProgram.fragmentShader = newFragmentShader.release();
layerProgram.passes.push_back(std::move(passProgram));
}
return true;
}
bool ShaderProgramCompiler::CompileDecodeShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
const char* fragmentSource = kDecodeFragmentShaderSource;
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
glCompileShader(newVertexShader.get());
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
return false;
}
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
glCompileShader(newFragmentShader.get());
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
return false;
}
ScopedGlProgram newProgram(glCreateProgram());
glAttachShader(newProgram.get(), newVertexShader.get());
glAttachShader(newProgram.get(), newFragmentShader.get());
glLinkProgram(newProgram.get());
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
return false;
}
mRenderer.DestroyDecodeShaderProgram();
mRenderer.SetDecodeShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
return true;
}
bool ShaderProgramCompiler::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
{
GLsizei errorBufferSize = 0;
GLint compileResult = GL_FALSE;
GLint linkResult = GL_FALSE;
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
const char* fragmentSource = kOutputPackFragmentShaderSource;
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
glCompileShader(newVertexShader.get());
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
return false;
}
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
glCompileShader(newFragmentShader.get());
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
if (compileResult == GL_FALSE)
{
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
return false;
}
ScopedGlProgram newProgram(glCreateProgram());
glAttachShader(newProgram.get(), newVertexShader.get());
glAttachShader(newProgram.get(), newFragmentShader.get());
glLinkProgram(newProgram.get());
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
return false;
}
glUseProgram(newProgram.get());
const GLint outputSamplerLocation = glGetUniformLocation(newProgram.get(), "uOutputRgb");
if (outputSamplerLocation >= 0)
glUniform1i(outputSamplerLocation, 0);
glUseProgram(0);
mRenderer.DestroyOutputPackShaderProgram();
mRenderer.SetOutputPackShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
return true;
}

View File

@@ -1,27 +0,0 @@
#pragma once
#include "OpenGLRenderer.h"
#include "RuntimeSnapshotProvider.h"
#include "ShaderTextureBindings.h"
#include <string>
#include <vector>
class ShaderProgramCompiler
{
public:
using LayerProgram = OpenGLRenderer::LayerProgram;
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider, ShaderTextureBindings& textureBindings);
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::vector<ShaderPassBuildSource>& passSources, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
private:
OpenGLRenderer& mRenderer;
RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
ShaderTextureBindings& mTextureBindings;
};

View File

@@ -1,256 +0,0 @@
#include "ShaderTextureBindings.h"
#include "GlRenderConstants.h"
#include "TextRasterizer.h"
#include "TextureAssetLoader.h"
#include <algorithm>
#include <filesystem>
namespace
{
std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId)
{
auto valueIt = state.parameterValues.find(parameterId);
return valueIt == state.parameterValues.end() ? std::string() : valueIt->second.textValue;
}
const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state, const ShaderParameterDefinition& definition)
{
if (!definition.fontId.empty())
{
for (const ShaderFontAsset& fontAsset : state.fontAssets)
{
if (fontAsset.id == definition.fontId)
return &fontAsset;
}
}
return state.fontAssets.empty() ? nullptr : &state.fontAssets.front();
}
}
bool ShaderTextureBindings::LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
{
return ::LoadTextureAsset(textureAsset, textureId, error);
}
void ShaderTextureBindings::CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings)
{
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
{
if (definition.type != ShaderParameterType::Text)
continue;
LayerProgram::TextBinding textBinding;
textBinding.parameterId = definition.id;
textBinding.samplerName = definition.id + "Texture";
textBinding.fontId = definition.fontId;
glGenTextures(1, &textBinding.texture);
glBindTexture(GL_TEXTURE_2D, textBinding.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_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
std::vector<unsigned char> empty(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight * 4, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextTextureWidth, kTextTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, empty.data());
glBindTexture(GL_TEXTURE_2D, 0);
textBindings.push_back(textBinding);
}
}
bool ShaderTextureBindings::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
{
const std::string text = TextValueForBinding(state, textBinding.parameterId);
if (text == textBinding.renderedText && textBinding.renderedWidth == kTextTextureWidth && textBinding.renderedHeight == kTextTextureHeight)
return true;
auto definitionIt = std::find_if(state.parameterDefinitions.begin(), state.parameterDefinitions.end(),
[&textBinding](const ShaderParameterDefinition& definition) { return definition.id == textBinding.parameterId; });
if (definitionIt == state.parameterDefinitions.end())
return true;
const ShaderFontAsset* fontAsset = FindFontAssetForParameter(state, *definitionIt);
std::filesystem::path fontPath;
if (fontAsset)
fontPath = fontAsset->path;
std::vector<unsigned char> sdf;
if (!RasterizeTextSdf(text, fontPath, sdf, error))
return false;
GLint previousActiveTexture = 0;
GLint previousUnpackBuffer = 0;
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
glActiveTexture(GL_TEXTURE0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data());
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
textBinding.renderedText = text;
textBinding.renderedWidth = kTextTextureWidth;
textBinding.renderedHeight = kTextTextureHeight;
return true;
}
GLint ShaderTextureBindings::FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const
{
GLint location = glGetUniformLocation(program, samplerName.c_str());
if (location >= 0)
return location;
return glGetUniformLocation(program, (samplerName + "_0").c_str());
}
GLuint ShaderTextureBindings::ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const
{
return state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
}
GLuint ShaderTextureBindings::ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const
{
return ResolveFeedbackTextureUnit(state, historyCap) + (state.feedback.enabled ? 1u : 0u);
}
void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const
{
const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap);
const GLint layerInputLocation = FindSamplerUniformLocation(program, "gLayerInput");
if (layerInputLocation >= 0)
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
const GLint videoInputLocation = FindSamplerUniformLocation(program, "gVideoInput");
if (videoInputLocation >= 0)
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
for (unsigned index = 0; index < historyCap; ++index)
{
const std::string sourceSamplerName = "gSourceHistory" + std::to_string(index);
const GLint sourceSamplerLocation = glGetUniformLocation(program, sourceSamplerName.c_str());
if (sourceSamplerLocation >= 0)
glUniform1i(sourceSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + index));
const std::string temporalSamplerName = "gTemporalHistory" + std::to_string(index);
const GLint temporalSamplerLocation = glGetUniformLocation(program, temporalSamplerName.c_str());
if (temporalSamplerLocation >= 0)
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
}
if (state.feedback.enabled)
{
const GLint feedbackSamplerLocation = FindSamplerUniformLocation(program, "gFeedbackState");
if (feedbackSamplerLocation >= 0)
glUniform1i(feedbackSamplerLocation, static_cast<GLint>(ResolveFeedbackTextureUnit(state, historyCap)));
}
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
{
const GLint textureSamplerLocation = FindSamplerUniformLocation(program, passProgram.textureBindings[index].samplerName);
if (textureSamplerLocation >= 0)
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
}
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(passProgram.textureBindings.size());
for (std::size_t index = 0; index < passProgram.textBindings.size(); ++index)
{
const GLint textSamplerLocation = FindSamplerUniformLocation(program, passProgram.textBindings[index].samplerName);
if (textSamplerLocation >= 0)
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
}
}
ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan(
const PassProgram& passProgram,
GLuint layerInputTexture,
GLuint originalLayerInputTexture,
const RuntimeRenderState& state,
GLuint feedbackTexture,
const std::vector<GLuint>& sourceHistoryTextures,
const std::vector<GLuint>& temporalHistoryTextures) const
{
RuntimeTextureBindingPlan plan;
plan.bindings.push_back({ "originalLayerInput", "gLayerInput", originalLayerInputTexture, kLayerInputTextureUnit });
plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit });
for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index)
{
plan.bindings.push_back({
"sourceHistory",
"gSourceHistory" + std::to_string(index),
sourceHistoryTextures[index],
kSourceHistoryTextureUnitBase + static_cast<GLuint>(index)
});
}
const GLuint temporalBase = kSourceHistoryTextureUnitBase + static_cast<GLuint>(sourceHistoryTextures.size());
for (std::size_t index = 0; index < temporalHistoryTextures.size(); ++index)
{
plan.bindings.push_back({
"temporalHistory",
"gTemporalHistory" + std::to_string(index),
temporalHistoryTextures[index],
temporalBase + static_cast<GLuint>(index)
});
}
const GLuint feedbackTextureUnit = ResolveFeedbackTextureUnit(state, static_cast<unsigned>(sourceHistoryTextures.size()));
if (state.feedback.enabled)
{
plan.bindings.push_back({
"feedbackState",
"gFeedbackState",
feedbackTexture,
feedbackTextureUnit
});
}
const GLuint shaderTextureBase = passProgram.shaderTextureBase != 0
? passProgram.shaderTextureBase
: feedbackTextureUnit + (state.feedback.enabled ? 1u : 0u);
for (std::size_t index = 0; index < passProgram.textureBindings.size(); ++index)
{
const LayerProgram::TextureBinding& textureBinding = passProgram.textureBindings[index];
plan.bindings.push_back({
"shaderTexture",
textureBinding.samplerName,
textureBinding.texture,
shaderTextureBase + static_cast<GLuint>(index)
});
}
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(passProgram.textureBindings.size());
for (std::size_t index = 0; index < passProgram.textBindings.size(); ++index)
{
const LayerProgram::TextBinding& textBinding = passProgram.textBindings[index];
plan.bindings.push_back({
"textTexture",
textBinding.samplerName,
textBinding.texture,
textTextureBase + static_cast<GLuint>(index)
});
}
return plan;
}
void ShaderTextureBindings::BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const
{
for (const RuntimeTextureBinding& binding : plan.bindings)
{
glActiveTexture(GL_TEXTURE0 + binding.textureUnit);
glBindTexture(GL_TEXTURE_2D, binding.texture);
}
glActiveTexture(GL_TEXTURE0);
}
void ShaderTextureBindings::UnbindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const
{
for (const RuntimeTextureBinding& binding : plan.bindings)
{
glActiveTexture(GL_TEXTURE0 + binding.textureUnit);
glBindTexture(GL_TEXTURE_2D, 0);
}
glActiveTexture(GL_TEXTURE0);
}

View File

@@ -1,45 +0,0 @@
#pragma once
#include "OpenGLRenderer.h"
#include "ShaderTypes.h"
#include <string>
#include <vector>
class ShaderTextureBindings
{
public:
using LayerProgram = OpenGLRenderer::LayerProgram;
using PassProgram = OpenGLRenderer::LayerProgram::PassProgram;
struct RuntimeTextureBinding
{
std::string semanticName;
std::string samplerName;
GLuint texture = 0;
GLuint textureUnit = 0;
};
struct RuntimeTextureBindingPlan
{
std::vector<RuntimeTextureBinding> bindings;
};
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings);
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const;
GLuint ResolveFeedbackTextureUnit(const RuntimeRenderState& state, unsigned historyCap) const;
GLuint ResolveShaderTextureBase(const RuntimeRenderState& state, unsigned historyCap) const;
void AssignLayerSamplerUniforms(GLuint program, const RuntimeRenderState& state, const PassProgram& passProgram, unsigned historyCap) const;
RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan(
const PassProgram& passProgram,
GLuint layerInputTexture,
GLuint originalLayerInputTexture,
const RuntimeRenderState& state,
GLuint feedbackTexture,
const std::vector<GLuint>& sourceHistoryTextures,
const std::vector<GLuint>& temporalHistoryTextures) const;
void BindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;
void UnbindRuntimeTexturePlan(const RuntimeTextureBindingPlan& plan) const;
};

View File

@@ -1,48 +0,0 @@
#pragma once
#include <cstddef>
#include <cstring>
#include <vector>
inline std::size_t AlignStd140(std::size_t offset, std::size_t alignment)
{
const std::size_t mask = alignment - 1;
return (offset + mask) & ~mask;
}
template <typename TValue>
inline void AppendStd140Value(std::vector<unsigned char>& buffer, std::size_t alignment, const TValue& value)
{
const std::size_t offset = AlignStd140(buffer.size(), alignment);
if (buffer.size() < offset + sizeof(TValue))
buffer.resize(offset + sizeof(TValue), 0);
std::memcpy(buffer.data() + offset, &value, sizeof(TValue));
}
inline void AppendStd140Float(std::vector<unsigned char>& buffer, float value)
{
AppendStd140Value(buffer, 4, value);
}
inline void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
{
AppendStd140Value(buffer, 4, value);
}
inline void AppendStd140Vec2(std::vector<unsigned char>& buffer, float x, float y)
{
const std::size_t offset = AlignStd140(buffer.size(), 8);
if (buffer.size() < offset + sizeof(float) * 2)
buffer.resize(offset + sizeof(float) * 2, 0);
float values[2] = { x, y };
std::memcpy(buffer.data() + offset, values, sizeof(values));
}
inline void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, float z, float w)
{
const std::size_t offset = AlignStd140(buffer.size(), 16);
if (buffer.size() < offset + sizeof(float) * 4)
buffer.resize(offset + sizeof(float) * 4, 0);
float values[4] = { x, y, z, w };
std::memcpy(buffer.data() + offset, values, sizeof(values));
}

View File

@@ -1,243 +0,0 @@
#include "TextRasterizer.h"
#include <windows.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <gdiplus.h>
#include <memory>
namespace
{
constexpr int kTextSdfSpread = 20;
constexpr float kTextFontPixelSize = 144.0f;
constexpr float kTextLayoutPadding = 48.0f;
constexpr float kSdfInfinity = 1.0e20f;
class GdiplusSession
{
public:
GdiplusSession()
{
Gdiplus::GdiplusStartupInput startupInput;
mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok;
}
~GdiplusSession()
{
if (mStarted)
Gdiplus::GdiplusShutdown(mToken);
}
GdiplusSession(const GdiplusSession&) = delete;
GdiplusSession& operator=(const GdiplusSession&) = delete;
bool started() const { return mStarted; }
private:
ULONG_PTR mToken = 0;
bool mStarted = false;
};
std::wstring Utf8ToWide(const std::string& text)
{
if (text.empty())
return std::wstring();
const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0);
if (required <= 1)
return std::wstring();
std::wstring wide(static_cast<std::size_t>(required - 1), L'\0');
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required);
return wide;
}
void DistanceTransform1D(const std::vector<float>& input, std::vector<float>& output, unsigned count)
{
std::vector<unsigned> locations(count, 0);
std::vector<float> boundaries(static_cast<std::size_t>(count) + 1, 0.0f);
unsigned segment = 0;
locations[0] = 0;
boundaries[0] = -kSdfInfinity;
boundaries[1] = kSdfInfinity;
for (unsigned q = 1; q < count; ++q)
{
float intersection = 0.0f;
for (;;)
{
const unsigned location = locations[segment];
intersection =
((input[q] + static_cast<float>(q * q)) - (input[location] + static_cast<float>(location * location))) /
(2.0f * static_cast<float>(q) - 2.0f * static_cast<float>(location));
if (intersection > boundaries[segment] || segment == 0)
break;
--segment;
}
++segment;
locations[segment] = q;
boundaries[segment] = intersection;
boundaries[segment + 1] = kSdfInfinity;
}
segment = 0;
for (unsigned q = 0; q < count; ++q)
{
while (boundaries[segment + 1] < static_cast<float>(q))
++segment;
const unsigned location = locations[segment];
const float delta = static_cast<float>(q) - static_cast<float>(location);
output[q] = delta * delta + input[location];
}
}
std::vector<float> DistanceTransform2D(const std::vector<unsigned char>& targetMask, unsigned width, unsigned height)
{
std::vector<float> rowInput(width, 0.0f);
std::vector<float> rowOutput(width, 0.0f);
std::vector<float> columnInput(height, 0.0f);
std::vector<float> columnOutput(height, 0.0f);
std::vector<float> rowDistance(static_cast<std::size_t>(width) * height, 0.0f);
std::vector<float> distance(static_cast<std::size_t>(width) * height, 0.0f);
for (unsigned y = 0; y < height; ++y)
{
for (unsigned x = 0; x < width; ++x)
rowInput[x] = targetMask[static_cast<std::size_t>(y) * width + x] ? 0.0f : kSdfInfinity;
DistanceTransform1D(rowInput, rowOutput, width);
for (unsigned x = 0; x < width; ++x)
rowDistance[static_cast<std::size_t>(y) * width + x] = rowOutput[x];
}
for (unsigned x = 0; x < width; ++x)
{
for (unsigned y = 0; y < height; ++y)
columnInput[y] = rowDistance[static_cast<std::size_t>(y) * width + x];
DistanceTransform1D(columnInput, columnOutput, height);
for (unsigned y = 0; y < height; ++y)
distance[static_cast<std::size_t>(y) * width + x] = columnOutput[y];
}
return distance;
}
std::vector<unsigned char> BuildTextSdfTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
{
std::vector<unsigned char> insideMask(static_cast<std::size_t>(width) * height, 0);
std::vector<unsigned char> outsideMask(static_cast<std::size_t>(width) * height, 0);
for (std::size_t index = 0; index < alpha.size(); ++index)
{
const bool inside = alpha[index] > 127;
insideMask[index] = inside ? 1 : 0;
outsideMask[index] = inside ? 0 : 1;
}
const std::vector<float> distanceToInside = DistanceTransform2D(insideMask, width, height);
const std::vector<float> distanceToOutside = DistanceTransform2D(outsideMask, width, height);
std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);
for (unsigned y = 0; y < height; ++y)
{
const unsigned flippedY = height - 1 - y;
for (unsigned x = 0; x < width; ++x)
{
const std::size_t source = static_cast<std::size_t>(y) * width + x;
const float signedDistance = std::sqrt(distanceToOutside[source]) - std::sqrt(distanceToInside[source]);
const float normalized = std::clamp(
0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2),
0.0f,
1.0f);
const unsigned char value = static_cast<unsigned char>(normalized * 255.0f + 0.5f);
const std::size_t out = (static_cast<std::size_t>(flippedY) * width + x) * 4;
sdf[out + 0] = value;
sdf[out + 1] = value;
sdf[out + 2] = value;
sdf[out + 3] = value;
}
}
return sdf;
}
}
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error)
{
GdiplusSession gdiplus;
if (!gdiplus.started())
{
error = "Could not start GDI+ for text rendering.";
return false;
}
Gdiplus::PrivateFontCollection fontCollection;
Gdiplus::FontFamily fallbackFamily(L"Arial");
Gdiplus::FontFamily* fontFamily = &fallbackFamily;
std::unique_ptr<Gdiplus::FontFamily[]> families;
const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring();
if (!wideFontPath.empty())
{
if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok)
{
error = "Could not load packaged font file for text rendering: " + fontPath.string();
return false;
}
const INT familyCount = fontCollection.GetFamilyCount();
if (familyCount <= 0)
{
error = "Packaged font did not contain a usable font family: " + fontPath.string();
return false;
}
families.reset(new Gdiplus::FontFamily[familyCount]);
INT found = 0;
if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0)
{
error = "Could not read the packaged font family: " + fontPath.string();
return false;
}
fontFamily = &families[0];
}
Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB);
Gdiplus::Graphics graphics(&bitmap);
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics.Clear(Gdiplus::Color(255, 0, 0, 0));
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255));
Gdiplus::StringFormat format;
format.SetAlignment(Gdiplus::StringAlignmentNear);
format.SetLineAlignment(Gdiplus::StringAlignmentCenter);
format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces);
const Gdiplus::RectF layout(
kTextLayoutPadding,
0.0f,
static_cast<Gdiplus::REAL>(kTextTextureWidth) - (kTextLayoutPadding * 2.0f),
static_cast<Gdiplus::REAL>(kTextTextureHeight));
const std::wstring wideText = Utf8ToWide(text);
graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush);
std::vector<unsigned char> alpha(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight, 0);
for (unsigned y = 0; y < kTextTextureHeight; ++y)
{
for (unsigned x = 0; x < kTextTextureWidth; ++x)
{
Gdiplus::Color pixel;
bitmap.GetPixel(x, y, &pixel);
BYTE luminance = pixel.GetRed();
if (pixel.GetGreen() > luminance)
luminance = pixel.GetGreen();
if (pixel.GetBlue() > luminance)
luminance = pixel.GetBlue();
alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(luminance);
}
}
sdf = BuildTextSdfTexture(alpha, kTextTextureWidth, kTextTextureHeight);
return true;
}

View File

@@ -1,10 +0,0 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
constexpr unsigned kTextTextureWidth = 4096;
constexpr unsigned kTextTextureHeight = 512;
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error);

View File

@@ -1,222 +0,0 @@
#include "TextureAssetLoader.h"
#include <windows.h>
#include <wincodec.h>
#include <atlbase.h>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#ifndef GL_RGBA32F
#define GL_RGBA32F 0x8814
#endif
namespace
{
std::string LowercaseExtension(const std::filesystem::path& path)
{
std::string extension = path.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(),
[](unsigned char value) { return static_cast<char>(std::tolower(value)); });
return extension;
}
bool LoadCubeTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
{
std::ifstream file(textureAsset.path);
if (!file)
{
error = "Could not open shader LUT asset: " + textureAsset.path.string();
return false;
}
unsigned lutSize = 0;
std::vector<float> values;
std::string line;
while (std::getline(file, line))
{
const std::size_t commentStart = line.find('#');
if (commentStart != std::string::npos)
line.resize(commentStart);
std::istringstream stream(line);
std::string firstToken;
if (!(stream >> firstToken))
continue;
if (firstToken == "TITLE" || firstToken == "DOMAIN_MIN" || firstToken == "DOMAIN_MAX")
continue;
if (firstToken == "LUT_3D_SIZE")
{
stream >> lutSize;
continue;
}
if (firstToken == "LUT_1D_SIZE")
{
error = "Only 3D .cube LUT assets are supported: " + textureAsset.path.string();
return false;
}
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
try
{
red = std::stof(firstToken);
}
catch (...)
{
error = "Unsupported .cube directive in shader LUT asset: " + firstToken;
return false;
}
if (!(stream >> green >> blue))
{
error = "Malformed RGB entry in shader LUT asset: " + textureAsset.path.string();
return false;
}
values.push_back(red);
values.push_back(green);
values.push_back(blue);
values.push_back(1.0f);
}
if (lutSize == 0)
{
error = "Shader LUT asset is missing LUT_3D_SIZE: " + textureAsset.path.string();
return false;
}
const std::size_t expectedFloats = static_cast<std::size_t>(lutSize) * lutSize * lutSize * 4;
if (values.size() != expectedFloats)
{
error = "Shader LUT asset entry count does not match LUT_3D_SIZE: " + textureAsset.path.string();
return false;
}
const GLsizei atlasWidth = static_cast<GLsizei>(lutSize * lutSize);
const GLsizei atlasHeight = static_cast<GLsizei>(lutSize);
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, atlasWidth, atlasHeight, 0, GL_RGBA, GL_FLOAT, values.data());
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
}
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
{
textureId = 0;
if (LowercaseExtension(textureAsset.path) == ".cube")
return LoadCubeTextureAsset(textureAsset, textureId, error);
HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE);
if (FAILED(comInitResult) && comInitResult != RPC_E_CHANGED_MODE)
{
error = "Could not initialize COM to load shader texture assets.";
return false;
}
CComPtr<IWICImagingFactory> imagingFactory;
HRESULT result = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&imagingFactory));
if (FAILED(result) || !imagingFactory)
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not create a WIC imaging factory to load shader texture assets.";
return false;
}
CComPtr<IWICBitmapDecoder> bitmapDecoder;
result = imagingFactory->CreateDecoderFromFilename(textureAsset.path.wstring().c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &bitmapDecoder);
if (FAILED(result) || !bitmapDecoder)
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not open shader texture asset: " + textureAsset.path.string();
return false;
}
CComPtr<IWICBitmapFrameDecode> bitmapFrame;
result = bitmapDecoder->GetFrame(0, &bitmapFrame);
if (FAILED(result) || !bitmapFrame)
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not decode the first frame of shader texture asset: " + textureAsset.path.string();
return false;
}
CComPtr<IWICFormatConverter> formatConverter;
result = imagingFactory->CreateFormatConverter(&formatConverter);
if (FAILED(result) || !formatConverter)
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not create a WIC format converter for shader texture asset: " + textureAsset.path.string();
return false;
}
result = formatConverter->Initialize(bitmapFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom);
if (FAILED(result))
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not convert shader texture asset to BGRA: " + textureAsset.path.string();
return false;
}
UINT width = 0;
UINT height = 0;
result = formatConverter->GetSize(&width, &height);
if (FAILED(result) || width == 0 || height == 0)
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Shader texture asset has an invalid size: " + textureAsset.path.string();
return false;
}
const UINT stride = width * 4;
std::vector<unsigned char> pixels(static_cast<std::size_t>(stride) * static_cast<std::size_t>(height));
result = formatConverter->CopyPixels(NULL, stride, static_cast<UINT>(pixels.size()), pixels.data());
if (FAILED(result))
{
if (shouldUninitializeCom)
CoUninitialize();
error = "Could not read shader texture pixels: " + textureAsset.path.string();
return false;
}
std::vector<unsigned char> flippedPixels(pixels.size());
for (UINT row = 0; row < height; ++row)
{
const std::size_t srcOffset = static_cast<std::size_t>(row) * stride;
const std::size_t dstOffset = static_cast<std::size_t>(height - 1 - row) * stride;
std::memcpy(flippedPixels.data() + dstOffset, pixels.data() + srcOffset, stride);
}
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
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, static_cast<GLsizei>(width), static_cast<GLsizei>(height), 0, GL_BGRA, GL_UNSIGNED_BYTE, flippedPixels.data());
glBindTexture(GL_TEXTURE_2D, 0);
if (shouldUninitializeCom)
CoUninitialize();
return true;
}

View File

@@ -1,11 +0,0 @@
#pragma once
#include "GLExtensions.h"
#include "ShaderTypes.h"
#include <windows.h>
#include <gl/gl.h>
#include <string>
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);

View File

@@ -1,160 +0,0 @@
#include "RenderCommandQueue.h"
void RenderCommandQueue::RequestPreviewPresent(const RenderPreviewPresentRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasPreviewPresentRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mPreviewPresentRequest = request;
mHasPreviewPresentRequest = true;
}
bool RenderCommandQueue::TryTakePreviewPresent(RenderPreviewPresentRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasPreviewPresentRequest)
return false;
request = mPreviewPresentRequest;
mPreviewPresentRequest = {};
mHasPreviewPresentRequest = false;
return true;
}
void RenderCommandQueue::RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasScreenshotCaptureRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mScreenshotCaptureRequest = request;
mHasScreenshotCaptureRequest = true;
}
bool RenderCommandQueue::TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasScreenshotCaptureRequest)
return false;
request = mScreenshotCaptureRequest;
mScreenshotCaptureRequest = {};
mHasScreenshotCaptureRequest = false;
return true;
}
void RenderCommandQueue::RequestInputUpload(const RenderInputUploadRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mHasInputUploadRequest)
++mCoalescedCount;
else
++mEnqueuedCount;
mInputUploadRequest = request;
mHasInputUploadRequest = true;
}
bool RenderCommandQueue::TryTakeInputUpload(RenderInputUploadRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mHasInputUploadRequest)
return false;
request = mInputUploadRequest;
mInputUploadRequest = {};
mHasInputUploadRequest = false;
return true;
}
void RenderCommandQueue::RequestOutputFrame(const RenderOutputFrameRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
mOutputFrameRequests.push_back(request);
++mEnqueuedCount;
}
bool RenderCommandQueue::TryTakeOutputFrame(RenderOutputFrameRequest& request)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mOutputFrameRequests.empty())
return false;
request = mOutputFrameRequests.front();
mOutputFrameRequests.pop_front();
return true;
}
void RenderCommandQueue::RequestRenderReset(RenderCommandResetScope scope)
{
if (scope == RenderCommandResetScope::None)
return;
std::lock_guard<std::mutex> lock(mMutex);
if (mRenderResetScope != RenderCommandResetScope::None)
++mCoalescedCount;
else
++mEnqueuedCount;
mRenderResetScope = MergeResetScopes(mRenderResetScope, scope);
}
bool RenderCommandQueue::TryTakeRenderReset(RenderCommandResetScope& scope)
{
std::lock_guard<std::mutex> lock(mMutex);
if (mRenderResetScope == RenderCommandResetScope::None)
return false;
scope = mRenderResetScope;
mRenderResetScope = RenderCommandResetScope::None;
return true;
}
RenderCommandQueueMetrics RenderCommandQueue::GetMetrics() const
{
std::lock_guard<std::mutex> lock(mMutex);
RenderCommandQueueMetrics metrics;
metrics.depth =
(mHasPreviewPresentRequest ? 1u : 0u) +
(mHasScreenshotCaptureRequest ? 1u : 0u) +
(mHasInputUploadRequest ? 1u : 0u) +
mOutputFrameRequests.size() +
(mRenderResetScope != RenderCommandResetScope::None ? 1u : 0u);
metrics.enqueuedCount = mEnqueuedCount;
metrics.coalescedCount = mCoalescedCount;
return metrics;
}
RenderCommandResetScope RenderCommandQueue::MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested)
{
if (current == RenderCommandResetScope::TemporalHistoryAndFeedback ||
requested == RenderCommandResetScope::TemporalHistoryAndFeedback)
{
return RenderCommandResetScope::TemporalHistoryAndFeedback;
}
if ((current == RenderCommandResetScope::TemporalHistoryOnly && requested == RenderCommandResetScope::ShaderFeedbackOnly) ||
(current == RenderCommandResetScope::ShaderFeedbackOnly && requested == RenderCommandResetScope::TemporalHistoryOnly))
{
return RenderCommandResetScope::TemporalHistoryAndFeedback;
}
if (current == RenderCommandResetScope::TemporalHistoryOnly ||
requested == RenderCommandResetScope::TemporalHistoryOnly)
{
return RenderCommandResetScope::TemporalHistoryOnly;
}
if (current == RenderCommandResetScope::ShaderFeedbackOnly ||
requested == RenderCommandResetScope::ShaderFeedbackOnly)
{
return RenderCommandResetScope::ShaderFeedbackOnly;
}
return RenderCommandResetScope::None;
}

View File

@@ -1,85 +0,0 @@
#pragma once
#include "VideoIOTypes.h"
#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <vector>
enum class RenderCommandResetScope
{
None,
ShaderFeedbackOnly,
TemporalHistoryOnly,
TemporalHistoryAndFeedback
};
struct RenderPreviewPresentRequest
{
unsigned outputFrameWidth = 0;
unsigned outputFrameHeight = 0;
};
struct RenderScreenshotCaptureRequest
{
unsigned width = 0;
unsigned height = 0;
};
struct RenderInputUploadRequest
{
VideoIOFrame inputFrame;
VideoIOState videoState;
std::vector<unsigned char> ownedBytes;
};
struct RenderOutputFrameRequest
{
VideoIOState videoState;
VideoIOCompletion completion;
};
struct RenderCommandQueueMetrics
{
std::size_t depth = 0;
uint64_t enqueuedCount = 0;
uint64_t coalescedCount = 0;
};
class RenderCommandQueue
{
public:
void RequestPreviewPresent(const RenderPreviewPresentRequest& request);
bool TryTakePreviewPresent(RenderPreviewPresentRequest& request);
void RequestScreenshotCapture(const RenderScreenshotCaptureRequest& request);
bool TryTakeScreenshotCapture(RenderScreenshotCaptureRequest& request);
void RequestInputUpload(const RenderInputUploadRequest& request);
bool TryTakeInputUpload(RenderInputUploadRequest& request);
void RequestOutputFrame(const RenderOutputFrameRequest& request);
bool TryTakeOutputFrame(RenderOutputFrameRequest& request);
void RequestRenderReset(RenderCommandResetScope scope);
bool TryTakeRenderReset(RenderCommandResetScope& scope);
RenderCommandQueueMetrics GetMetrics() const;
private:
static RenderCommandResetScope MergeResetScopes(RenderCommandResetScope current, RenderCommandResetScope requested);
mutable std::mutex mMutex;
bool mHasPreviewPresentRequest = false;
RenderPreviewPresentRequest mPreviewPresentRequest;
bool mHasScreenshotCaptureRequest = false;
RenderScreenshotCaptureRequest mScreenshotCaptureRequest;
bool mHasInputUploadRequest = false;
RenderInputUploadRequest mInputUploadRequest;
std::deque<RenderOutputFrameRequest> mOutputFrameRequests;
RenderCommandResetScope mRenderResetScope = RenderCommandResetScope::None;
uint64_t mEnqueuedCount = 0;
uint64_t mCoalescedCount = 0;
};