re organisation
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#include "OpenGLRenderPass.h"
|
||||
|
||||
#include "GlRenderConstants.h"
|
||||
|
||||
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
|
||||
{
|
||||
GLuint sourceTexture = mRenderer.DecodedTexture();
|
||||
GLuint sourceFrameBuffer = mRenderer.DecodeFramebuffer();
|
||||
for (std::size_t index = 0; index < layerStates.size() && index < layerPrograms.size(); ++index)
|
||||
{
|
||||
const std::size_t remaining = layerStates.size() - index;
|
||||
const bool writeToMain = (remaining % 2) == 1;
|
||||
RenderShaderProgram(
|
||||
sourceTexture,
|
||||
writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer(),
|
||||
layerPrograms[index],
|
||||
layerStates[index],
|
||||
inputFrameWidth,
|
||||
inputFrameHeight,
|
||||
historyCap,
|
||||
updateTextBinding,
|
||||
updateGlobalParams);
|
||||
if (layerStates[index].temporalHistorySource == TemporalHistorySource::PreLayerInput)
|
||||
mRenderer.TemporalHistory().PushPreLayerFramebuffer(layerStates[index].layerId, sourceFrameBuffer, inputFrameWidth, inputFrameHeight);
|
||||
sourceTexture = writeToMain ? mRenderer.CompositeTexture() : mRenderer.LayerTempTexture();
|
||||
sourceFrameBuffer = writeToMain ? mRenderer.CompositeFramebuffer() : mRenderer.LayerTempFramebuffer();
|
||||
}
|
||||
}
|
||||
|
||||
mRenderer.TemporalHistory().PushSourceFramebuffer(mRenderer.DecodeFramebuffer(), inputFrameWidth, inputFrameHeight);
|
||||
}
|
||||
|
||||
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 = glGetUniformLocation(mRenderer.DecodeProgram(), "uPackedVideoResolution");
|
||||
const GLint decodedResolutionLocation = glGetUniformLocation(mRenderer.DecodeProgram(), "uDecodedVideoResolution");
|
||||
const GLint inputPixelFormatLocation = glGetUniformLocation(mRenderer.DecodeProgram(), "uInputPixelFormat");
|
||||
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);
|
||||
}
|
||||
|
||||
void OpenGLRenderPass::RenderShaderProgram(
|
||||
GLuint sourceTexture,
|
||||
GLuint destinationFrameBuffer,
|
||||
LayerProgram& layerProgram,
|
||||
const RuntimeRenderState& state,
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
unsigned historyCap,
|
||||
const TextBindingUpdater& updateTextBinding,
|
||||
const GlobalParamsUpdater& updateGlobalParams)
|
||||
{
|
||||
for (LayerProgram::TextBinding& textBinding : layerProgram.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);
|
||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
||||
mRenderer.TemporalHistory().BindSamplers(state, sourceTexture, historyCap);
|
||||
BindLayerTextureAssets(layerProgram);
|
||||
glBindVertexArray(mRenderer.FullscreenVertexArray());
|
||||
glUseProgram(layerProgram.program);
|
||||
updateGlobalParams(state, mRenderer.TemporalHistory().SourceAvailableCount(), mRenderer.TemporalHistory().AvailableCountForLayer(state.layerId));
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
UnbindLayerTextureAssets(layerProgram, historyCap);
|
||||
}
|
||||
|
||||
void OpenGLRenderPass::BindLayerTextureAssets(const LayerProgram& layerProgram)
|
||||
{
|
||||
const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
|
||||
for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
||||
glBindTexture(GL_TEXTURE_2D, layerProgram.textureBindings[index].texture);
|
||||
}
|
||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(layerProgram.textureBindings.size());
|
||||
for (std::size_t index = 0; index < layerProgram.textBindings.size(); ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + textTextureBase + static_cast<GLuint>(index));
|
||||
glBindTexture(GL_TEXTURE_2D, layerProgram.textBindings[index].texture);
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void OpenGLRenderPass::UnbindLayerTextureAssets(const LayerProgram& layerProgram, unsigned historyCap)
|
||||
{
|
||||
for (unsigned index = 0; index < historyCap; ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + index);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
const GLuint shaderTextureBase = layerProgram.shaderTextureBase != 0 ? layerProgram.shaderTextureBase : kSourceHistoryTextureUnitBase;
|
||||
for (std::size_t index = 0; index < layerProgram.textureBindings.size() + layerProgram.textBindings.size(); ++index)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "ShaderTypes.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class OpenGLRenderPass
|
||||
{
|
||||
public:
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
using TextBindingUpdater = std::function<bool(const RuntimeRenderState&, LayerProgram::TextBinding&, std::string&)>;
|
||||
using GlobalParamsUpdater = std::function<bool(const RuntimeRenderState&, unsigned, unsigned)>;
|
||||
|
||||
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);
|
||||
void RenderShaderProgram(
|
||||
GLuint sourceTexture,
|
||||
GLuint destinationFrameBuffer,
|
||||
LayerProgram& layerProgram,
|
||||
const RuntimeRenderState& state,
|
||||
unsigned inputFrameWidth,
|
||||
unsigned inputFrameHeight,
|
||||
unsigned historyCap,
|
||||
const TextBindingUpdater& updateTextBinding,
|
||||
const GlobalParamsUpdater& updateGlobalParams);
|
||||
void BindLayerTextureAssets(const LayerProgram& layerProgram);
|
||||
void UnbindLayerTextureAssets(const LayerProgram& layerProgram, unsigned historyCap);
|
||||
|
||||
OpenGLRenderer& mRenderer;
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "OpenGLRenderPipeline.h"
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <gl/gl.h>
|
||||
|
||||
OpenGLRenderPipeline::OpenGLRenderPipeline(
|
||||
OpenGLRenderer& renderer,
|
||||
RuntimeHost& runtimeHost,
|
||||
RenderEffectCallback renderEffect,
|
||||
OutputReadyCallback outputReady,
|
||||
PaintCallback paint) :
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mRenderEffect(renderEffect),
|
||||
mOutputReady(outputReady),
|
||||
mPaint(paint)
|
||||
{
|
||||
}
|
||||
|
||||
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::V210)
|
||||
PackOutputForV210(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();
|
||||
mRuntimeHost.TrySetPerformanceStats(state.frameBudgetMilliseconds, renderMilliseconds);
|
||||
mRuntimeHost.TryAdvanceFrame();
|
||||
|
||||
ReadOutputFrame(state, outputFrame);
|
||||
if (mPaint)
|
||||
mPaint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::PackOutputForV210(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 = glGetUniformLocation(mRenderer.OutputPackProgram(), "uOutputVideoResolution");
|
||||
const GLint activeWordsLocation = glGetUniformLocation(mRenderer.OutputPackProgram(), "uActiveV210Words");
|
||||
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)));
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void OpenGLRenderPipeline::ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame)
|
||||
{
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
if (state.outputPixelFormat == VideoIOPixelFormat::V210)
|
||||
{
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputPackFramebuffer());
|
||||
glReadPixels(0, 0, state.outputPackTextureWidth, state.outputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, outputFrame.bytes);
|
||||
}
|
||||
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, outputFrame.bytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "VideoIOTypes.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
class OpenGLRenderer;
|
||||
class RuntimeHost;
|
||||
|
||||
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,
|
||||
RuntimeHost& runtimeHost,
|
||||
RenderEffectCallback renderEffect,
|
||||
OutputReadyCallback outputReady,
|
||||
PaintCallback paint);
|
||||
|
||||
bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);
|
||||
|
||||
private:
|
||||
void PackOutputForV210(const VideoIOState& state);
|
||||
void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
|
||||
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
RenderEffectCallback mRenderEffect;
|
||||
OutputReadyCallback mOutputReady;
|
||||
PaintCallback mPaint;
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "OpenGLVideoIOBridge.h"
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <gl/gl.h>
|
||||
|
||||
OpenGLVideoIOBridge::OpenGLVideoIOBridge(
|
||||
VideoIODevice& videoIO,
|
||||
OpenGLRenderer& renderer,
|
||||
OpenGLRenderPipeline& renderPipeline,
|
||||
RuntimeHost& runtimeHost,
|
||||
CRITICAL_SECTION& mutex,
|
||||
HDC hdc,
|
||||
HGLRC hglrc) :
|
||||
mVideoIO(videoIO),
|
||||
mRenderer(renderer),
|
||||
mRenderPipeline(renderPipeline),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mMutex(mutex),
|
||||
mHdc(hdc),
|
||||
mHglrc(hglrc)
|
||||
{
|
||||
}
|
||||
|
||||
void OpenGLVideoIOBridge::RecordFramePacing(VideoIOCompletionResult completionResult)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (mLastPlayoutCompletionTime != std::chrono::steady_clock::time_point())
|
||||
{
|
||||
mCompletionIntervalMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(now - mLastPlayoutCompletionTime).count();
|
||||
if (mSmoothedCompletionIntervalMilliseconds <= 0.0)
|
||||
mSmoothedCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds;
|
||||
else
|
||||
mSmoothedCompletionIntervalMilliseconds = mSmoothedCompletionIntervalMilliseconds * 0.9 + mCompletionIntervalMilliseconds * 0.1;
|
||||
if (mCompletionIntervalMilliseconds > mMaxCompletionIntervalMilliseconds)
|
||||
mMaxCompletionIntervalMilliseconds = mCompletionIntervalMilliseconds;
|
||||
}
|
||||
mLastPlayoutCompletionTime = now;
|
||||
|
||||
if (completionResult == VideoIOCompletionResult::DisplayedLate)
|
||||
++mLateFrameCount;
|
||||
else if (completionResult == VideoIOCompletionResult::Dropped)
|
||||
++mDroppedFrameCount;
|
||||
else if (completionResult == VideoIOCompletionResult::Flushed)
|
||||
++mFlushedFrameCount;
|
||||
|
||||
mRuntimeHost.TrySetFramePacingStats(
|
||||
mCompletionIntervalMilliseconds,
|
||||
mSmoothedCompletionIntervalMilliseconds,
|
||||
mMaxCompletionIntervalMilliseconds,
|
||||
mLateFrameCount,
|
||||
mDroppedFrameCount,
|
||||
mFlushedFrameCount);
|
||||
}
|
||||
|
||||
void OpenGLVideoIOBridge::VideoFrameArrived(const VideoIOFrame& inputFrame)
|
||||
{
|
||||
const VideoIOState& state = mVideoIO.State();
|
||||
mRuntimeHost.TrySetSignalStatus(!inputFrame.hasNoInputSource, state.inputFrameSize.width, state.inputFrameSize.height, state.inputDisplayModeName);
|
||||
|
||||
if (inputFrame.hasNoInputSource || inputFrame.bytes == nullptr)
|
||||
return; // don't transfer texture when there's no input
|
||||
|
||||
const long textureSize = inputFrame.rowBytes * static_cast<long>(inputFrame.height);
|
||||
|
||||
EnterCriticalSection(&mMutex);
|
||||
|
||||
wglMakeCurrent(mHdc, mHglrc); // make OpenGL context current in this thread
|
||||
|
||||
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());
|
||||
|
||||
// NULL for last arg indicates use current GL_PIXEL_UNPACK_BUFFER target as texture data.
|
||||
if (inputFrame.pixelFormat == VideoIOPixelFormat::V210)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, state.captureTextureWidth, state.inputFrameSize.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
LeaveCriticalSection(&mMutex);
|
||||
}
|
||||
|
||||
void OpenGLVideoIOBridge::PlayoutFrameCompleted(const VideoIOCompletion& completion)
|
||||
{
|
||||
RecordFramePacing(completion.result);
|
||||
|
||||
EnterCriticalSection(&mMutex);
|
||||
|
||||
VideoIOOutputFrame outputFrame;
|
||||
if (!mVideoIO.BeginOutputFrame(outputFrame))
|
||||
{
|
||||
LeaveCriticalSection(&mMutex);
|
||||
return;
|
||||
}
|
||||
const VideoIOState& state = mVideoIO.State();
|
||||
RenderPipelineFrameContext frameContext;
|
||||
frameContext.videoState = state;
|
||||
frameContext.completion = completion;
|
||||
|
||||
// make GL context current in this thread
|
||||
wglMakeCurrent(mHdc, mHglrc);
|
||||
|
||||
mRenderPipeline.RenderFrame(frameContext, outputFrame);
|
||||
|
||||
mVideoIO.EndOutputFrame(outputFrame);
|
||||
|
||||
mVideoIO.AccountForCompletionResult(completion.result);
|
||||
|
||||
// Schedule the next frame for playout
|
||||
mVideoIO.ScheduleOutputFrame(outputFrame);
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
LeaveCriticalSection(&mMutex);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderPipeline.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
class RuntimeHost;
|
||||
|
||||
class OpenGLVideoIOBridge
|
||||
{
|
||||
public:
|
||||
OpenGLVideoIOBridge(
|
||||
VideoIODevice& videoIO,
|
||||
OpenGLRenderer& renderer,
|
||||
OpenGLRenderPipeline& renderPipeline,
|
||||
RuntimeHost& runtimeHost,
|
||||
CRITICAL_SECTION& mutex,
|
||||
HDC hdc,
|
||||
HGLRC hglrc);
|
||||
|
||||
void VideoFrameArrived(const VideoIOFrame& inputFrame);
|
||||
void PlayoutFrameCompleted(const VideoIOCompletion& completion);
|
||||
|
||||
private:
|
||||
void RecordFramePacing(VideoIOCompletionResult completionResult);
|
||||
|
||||
VideoIODevice& mVideoIO;
|
||||
OpenGLRenderer& mRenderer;
|
||||
OpenGLRenderPipeline& mRenderPipeline;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
CRITICAL_SECTION& mMutex;
|
||||
HDC mHdc;
|
||||
HGLRC mHglrc;
|
||||
std::chrono::steady_clock::time_point mLastPlayoutCompletionTime;
|
||||
double mCompletionIntervalMilliseconds = 0.0;
|
||||
double mSmoothedCompletionIntervalMilliseconds = 0.0;
|
||||
double mMaxCompletionIntervalMilliseconds = 0.0;
|
||||
uint64_t mLateFrameCount = 0;
|
||||
uint64_t mDroppedFrameCount = 0;
|
||||
uint64_t mFlushedFrameCount = 0;
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
void WritePngFileAsync(
|
||||
const std::filesystem::path& outputPath,
|
||||
unsigned width,
|
||||
unsigned height,
|
||||
std::vector<unsigned char> rgbaPixels);
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
#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 layerRequiredUnits = kSourceHistoryTextureUnitBase + (state.isTemporal ? historyCap + historyCap : 0u) + 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#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);
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user