V2 working
This commit is contained in:
142
apps/RenderCadenceCompositor/render/Bgra8ReadbackPipeline.cpp
Normal file
142
apps/RenderCadenceCompositor/render/Bgra8ReadbackPipeline.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "Bgra8ReadbackPipeline.h"
|
||||
|
||||
#include "../frames/SystemFrameTypes.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
Bgra8ReadbackPipeline::~Bgra8ReadbackPipeline()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool Bgra8ReadbackPipeline::Initialize(unsigned width, unsigned height, std::size_t pboDepth)
|
||||
{
|
||||
Shutdown();
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mRowBytes = VideoIORowBytes(VideoIOPixelFormat::Bgra8, width);
|
||||
if (mWidth == 0 || mHeight == 0 || mRowBytes == 0)
|
||||
return false;
|
||||
|
||||
if (!CreateRenderTarget())
|
||||
{
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t byteCount = static_cast<std::size_t>(mRowBytes) * static_cast<std::size_t>(mHeight);
|
||||
if (!mPboRing.Initialize(pboDepth, byteCount))
|
||||
{
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bgra8ReadbackPipeline::Shutdown()
|
||||
{
|
||||
mPboRing.Shutdown();
|
||||
DestroyRenderTarget();
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
mRowBytes = 0;
|
||||
}
|
||||
|
||||
bool Bgra8ReadbackPipeline::RenderAndQueue(uint64_t frameIndex, const RenderCallback& renderFrame)
|
||||
{
|
||||
if (mFramebuffer == 0 || !renderFrame)
|
||||
return false;
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
||||
renderFrame(frameIndex);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
return mPboRing.QueueReadback(mFramebuffer, mWidth, mHeight, frameIndex);
|
||||
}
|
||||
|
||||
void Bgra8ReadbackPipeline::ConsumeCompleted(
|
||||
const AcquireFrameCallback& acquireFrame,
|
||||
const PublishFrameCallback& publishFrame,
|
||||
const CounterCallback& onAcquireMiss,
|
||||
const CounterCallback& onCompleted)
|
||||
{
|
||||
if (!acquireFrame || !publishFrame)
|
||||
return;
|
||||
|
||||
PboReadbackRing::CompletedReadback readback;
|
||||
while (mPboRing.TryAcquireCompleted(readback))
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, readback.pbo);
|
||||
void* mapped = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
if (!mapped)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
mPboRing.ReleaseCompleted(readback);
|
||||
continue;
|
||||
}
|
||||
|
||||
SystemFrame frame;
|
||||
if (acquireFrame(frame))
|
||||
{
|
||||
const std::size_t byteCount = static_cast<std::size_t>(frame.rowBytes) * static_cast<std::size_t>(frame.height);
|
||||
if (frame.bytes != nullptr && byteCount <= readback.byteCount)
|
||||
{
|
||||
std::memcpy(frame.bytes, mapped, byteCount);
|
||||
frame.frameIndex = readback.frameIndex;
|
||||
frame.pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
publishFrame(frame);
|
||||
if (onCompleted)
|
||||
onCompleted();
|
||||
}
|
||||
}
|
||||
else if (onAcquireMiss)
|
||||
{
|
||||
onAcquireMiss();
|
||||
}
|
||||
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
mPboRing.ReleaseCompleted(readback);
|
||||
}
|
||||
}
|
||||
|
||||
bool Bgra8ReadbackPipeline::CreateRenderTarget()
|
||||
{
|
||||
glGenFramebuffers(1, &mFramebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
||||
|
||||
glGenTextures(1, &mTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, mTexture);
|
||||
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);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
static_cast<GLsizei>(mWidth),
|
||||
static_cast<GLsizei>(mHeight),
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
nullptr);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
|
||||
|
||||
const bool complete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
return complete;
|
||||
}
|
||||
|
||||
void Bgra8ReadbackPipeline::DestroyRenderTarget()
|
||||
{
|
||||
if (mFramebuffer != 0)
|
||||
glDeleteFramebuffers(1, &mFramebuffer);
|
||||
if (mTexture != 0)
|
||||
glDeleteTextures(1, &mTexture);
|
||||
mFramebuffer = 0;
|
||||
mTexture = 0;
|
||||
}
|
||||
52
apps/RenderCadenceCompositor/render/Bgra8ReadbackPipeline.h
Normal file
52
apps/RenderCadenceCompositor/render/Bgra8ReadbackPipeline.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "PboReadbackRing.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
struct SystemFrame;
|
||||
|
||||
class Bgra8ReadbackPipeline
|
||||
{
|
||||
public:
|
||||
using RenderCallback = std::function<void(uint64_t frameIndex)>;
|
||||
using AcquireFrameCallback = std::function<bool(SystemFrame& frame)>;
|
||||
using PublishFrameCallback = std::function<bool(const SystemFrame& frame)>;
|
||||
using CounterCallback = std::function<void()>;
|
||||
|
||||
Bgra8ReadbackPipeline() = default;
|
||||
Bgra8ReadbackPipeline(const Bgra8ReadbackPipeline&) = delete;
|
||||
Bgra8ReadbackPipeline& operator=(const Bgra8ReadbackPipeline&) = delete;
|
||||
~Bgra8ReadbackPipeline();
|
||||
|
||||
bool Initialize(unsigned width, unsigned height, std::size_t pboDepth);
|
||||
void Shutdown();
|
||||
|
||||
bool RenderAndQueue(uint64_t frameIndex, const RenderCallback& renderFrame);
|
||||
void ConsumeCompleted(
|
||||
const AcquireFrameCallback& acquireFrame,
|
||||
const PublishFrameCallback& publishFrame,
|
||||
const CounterCallback& onAcquireMiss = {},
|
||||
const CounterCallback& onCompleted = {});
|
||||
|
||||
GLuint Framebuffer() const { return mFramebuffer; }
|
||||
unsigned Width() const { return mWidth; }
|
||||
unsigned Height() const { return mHeight; }
|
||||
unsigned RowBytes() const { return mRowBytes; }
|
||||
VideoIOPixelFormat PixelFormat() const { return VideoIOPixelFormat::Bgra8; }
|
||||
uint64_t PboQueueMisses() const { return mPboRing.QueueMisses(); }
|
||||
|
||||
private:
|
||||
bool CreateRenderTarget();
|
||||
void DestroyRenderTarget();
|
||||
|
||||
unsigned mWidth = 0;
|
||||
unsigned mHeight = 0;
|
||||
unsigned mRowBytes = 0;
|
||||
GLuint mFramebuffer = 0;
|
||||
GLuint mTexture = 0;
|
||||
PboReadbackRing mPboRing;
|
||||
};
|
||||
138
apps/RenderCadenceCompositor/render/PboReadbackRing.cpp
Normal file
138
apps/RenderCadenceCompositor/render/PboReadbackRing.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "PboReadbackRing.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
PboReadbackRing::~PboReadbackRing()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool PboReadbackRing::Initialize(std::size_t depth, std::size_t byteCount)
|
||||
{
|
||||
Shutdown();
|
||||
if (depth == 0 || byteCount == 0)
|
||||
return false;
|
||||
|
||||
mSlots.resize(depth);
|
||||
mByteCount = byteCount;
|
||||
for (Slot& slot : mSlots)
|
||||
{
|
||||
glGenBuffers(1, &slot.pbo);
|
||||
if (slot.pbo == 0)
|
||||
{
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(mByteCount), nullptr, GL_STREAM_READ);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PboReadbackRing::Shutdown()
|
||||
{
|
||||
for (Slot& slot : mSlots)
|
||||
{
|
||||
if (slot.fence)
|
||||
glDeleteSync(slot.fence);
|
||||
if (slot.pbo != 0)
|
||||
glDeleteBuffers(1, &slot.pbo);
|
||||
slot = {};
|
||||
}
|
||||
mSlots.clear();
|
||||
mWriteIndex = 0;
|
||||
mReadIndex = 0;
|
||||
mByteCount = 0;
|
||||
}
|
||||
|
||||
bool PboReadbackRing::QueueReadback(GLuint framebuffer, unsigned width, unsigned height, uint64_t frameIndex)
|
||||
{
|
||||
if (mSlots.empty())
|
||||
return false;
|
||||
|
||||
Slot& slot = mSlots[mWriteIndex];
|
||||
if (slot.inFlight || slot.acquired)
|
||||
{
|
||||
++mQueueMisses;
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(mByteCount), nullptr, GL_STREAM_READ);
|
||||
glReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
|
||||
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
slot.inFlight = slot.fence != nullptr;
|
||||
slot.frameIndex = frameIndex;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
if (!slot.inFlight)
|
||||
return false;
|
||||
|
||||
mWriteIndex = (mWriteIndex + 1) % mSlots.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PboReadbackRing::TryAcquireCompleted(CompletedReadback& readback)
|
||||
{
|
||||
if (mSlots.empty())
|
||||
return false;
|
||||
|
||||
for (std::size_t checked = 0; checked < mSlots.size(); ++checked)
|
||||
{
|
||||
Slot& slot = mSlots[mReadIndex];
|
||||
if (!slot.inFlight || slot.acquired || slot.fence == nullptr)
|
||||
{
|
||||
mReadIndex = (mReadIndex + 1) % mSlots.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
const GLenum waitResult = glClientWaitSync(slot.fence, 0, 0);
|
||||
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
||||
return false;
|
||||
|
||||
slot.acquired = true;
|
||||
readback.pbo = slot.pbo;
|
||||
readback.frameIndex = slot.frameIndex;
|
||||
readback.byteCount = mByteCount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PboReadbackRing::ReleaseCompleted(const CompletedReadback& readback)
|
||||
{
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
Slot& slot = mSlots[index];
|
||||
if (!slot.acquired || slot.pbo != readback.pbo)
|
||||
continue;
|
||||
ResetSlot(slot);
|
||||
mReadIndex = (index + 1) % mSlots.size();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PboReadbackRing::DrainCompleted()
|
||||
{
|
||||
for (std::size_t pass = 0; pass < mSlots.size() * 2; ++pass)
|
||||
{
|
||||
CompletedReadback readback;
|
||||
if (!TryAcquireCompleted(readback))
|
||||
break;
|
||||
ReleaseCompleted(readback);
|
||||
}
|
||||
}
|
||||
|
||||
void PboReadbackRing::ResetSlot(Slot& slot)
|
||||
{
|
||||
if (slot.fence)
|
||||
glDeleteSync(slot.fence);
|
||||
slot.fence = nullptr;
|
||||
slot.inFlight = false;
|
||||
slot.acquired = false;
|
||||
slot.frameIndex = 0;
|
||||
}
|
||||
52
apps/RenderCadenceCompositor/render/PboReadbackRing.h
Normal file
52
apps/RenderCadenceCompositor/render/PboReadbackRing.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "GLExtensions.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class PboReadbackRing
|
||||
{
|
||||
public:
|
||||
struct CompletedReadback
|
||||
{
|
||||
GLuint pbo = 0;
|
||||
uint64_t frameIndex = 0;
|
||||
std::size_t byteCount = 0;
|
||||
};
|
||||
|
||||
PboReadbackRing() = default;
|
||||
PboReadbackRing(const PboReadbackRing&) = delete;
|
||||
PboReadbackRing& operator=(const PboReadbackRing&) = delete;
|
||||
~PboReadbackRing();
|
||||
|
||||
bool Initialize(std::size_t depth, std::size_t byteCount);
|
||||
void Shutdown();
|
||||
|
||||
bool QueueReadback(GLuint framebuffer, unsigned width, unsigned height, uint64_t frameIndex);
|
||||
bool TryAcquireCompleted(CompletedReadback& readback);
|
||||
void ReleaseCompleted(const CompletedReadback& readback);
|
||||
void DrainCompleted();
|
||||
|
||||
std::size_t Depth() const { return mSlots.size(); }
|
||||
uint64_t QueueMisses() const { return mQueueMisses; }
|
||||
|
||||
private:
|
||||
struct Slot
|
||||
{
|
||||
GLuint pbo = 0;
|
||||
GLsync fence = nullptr;
|
||||
bool inFlight = false;
|
||||
bool acquired = false;
|
||||
uint64_t frameIndex = 0;
|
||||
};
|
||||
|
||||
void ResetSlot(Slot& slot);
|
||||
|
||||
std::vector<Slot> mSlots;
|
||||
std::size_t mWriteIndex = 0;
|
||||
std::size_t mReadIndex = 0;
|
||||
std::size_t mByteCount = 0;
|
||||
uint64_t mQueueMisses = 0;
|
||||
};
|
||||
45
apps/RenderCadenceCompositor/render/RenderCadenceClock.cpp
Normal file
45
apps/RenderCadenceCompositor/render/RenderCadenceClock.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "RenderCadenceClock.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
RenderCadenceClock::RenderCadenceClock(double frameDurationMilliseconds)
|
||||
{
|
||||
mFrameDuration = std::chrono::duration_cast<Duration>(std::chrono::duration<double, std::milli>(frameDurationMilliseconds));
|
||||
if (mFrameDuration <= Duration::zero())
|
||||
mFrameDuration = std::chrono::milliseconds(16);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void RenderCadenceClock::Reset(TimePoint now)
|
||||
{
|
||||
mNextRenderTime = now;
|
||||
mOverrunCount = 0;
|
||||
mSkippedFrameCount = 0;
|
||||
}
|
||||
|
||||
RenderCadenceClock::Tick RenderCadenceClock::Poll(TimePoint now)
|
||||
{
|
||||
Tick tick;
|
||||
if (now < mNextRenderTime)
|
||||
{
|
||||
tick.sleepFor = std::min(Duration(std::chrono::milliseconds(1)), mNextRenderTime - now);
|
||||
return tick;
|
||||
}
|
||||
|
||||
tick.due = true;
|
||||
const Duration lateBy = now - mNextRenderTime;
|
||||
if (lateBy > mFrameDuration)
|
||||
{
|
||||
tick.skippedFrames = static_cast<uint64_t>(lateBy / mFrameDuration);
|
||||
++mOverrunCount;
|
||||
mSkippedFrameCount += tick.skippedFrames;
|
||||
}
|
||||
return tick;
|
||||
}
|
||||
|
||||
void RenderCadenceClock::MarkRendered(TimePoint now)
|
||||
{
|
||||
mNextRenderTime += mFrameDuration;
|
||||
if (now - mNextRenderTime > mFrameDuration * 4)
|
||||
mNextRenderTime = now + mFrameDuration;
|
||||
}
|
||||
36
apps/RenderCadenceCompositor/render/RenderCadenceClock.h
Normal file
36
apps/RenderCadenceCompositor/render/RenderCadenceClock.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
class RenderCadenceClock
|
||||
{
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Duration = Clock::duration;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
struct Tick
|
||||
{
|
||||
bool due = false;
|
||||
uint64_t skippedFrames = 0;
|
||||
Duration sleepFor = Duration::zero();
|
||||
};
|
||||
|
||||
explicit RenderCadenceClock(double frameDurationMilliseconds = 1000.0 / 60.0);
|
||||
|
||||
void Reset(TimePoint now = Clock::now());
|
||||
Tick Poll(TimePoint now = Clock::now());
|
||||
void MarkRendered(TimePoint now = Clock::now());
|
||||
|
||||
Duration FrameDuration() const { return mFrameDuration; }
|
||||
TimePoint NextRenderTime() const { return mNextRenderTime; }
|
||||
uint64_t OverrunCount() const { return mOverrunCount; }
|
||||
uint64_t SkippedFrameCount() const { return mSkippedFrameCount; }
|
||||
|
||||
private:
|
||||
Duration mFrameDuration;
|
||||
TimePoint mNextRenderTime = Clock::now();
|
||||
uint64_t mOverrunCount = 0;
|
||||
uint64_t mSkippedFrameCount = 0;
|
||||
};
|
||||
181
apps/RenderCadenceCompositor/render/RenderThread.cpp
Normal file
181
apps/RenderCadenceCompositor/render/RenderThread.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "RenderThread.h"
|
||||
|
||||
#include "../frames/SystemFrameExchange.h"
|
||||
#include "../frames/SystemFrameTypes.h"
|
||||
#include "../platform/HiddenGlWindow.h"
|
||||
#include "Bgra8ReadbackPipeline.h"
|
||||
#include "GLExtensions.h"
|
||||
#include "SimpleMotionRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
|
||||
mFrameExchange(frameExchange),
|
||||
mConfig(config)
|
||||
{
|
||||
}
|
||||
|
||||
RenderThread::~RenderThread()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool RenderThread::Start(std::string& error)
|
||||
{
|
||||
if (mThread.joinable())
|
||||
return true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||
mStarted = false;
|
||||
mStartupError.clear();
|
||||
}
|
||||
|
||||
mStopping.store(false, std::memory_order_release);
|
||||
mThread = std::thread([this]() { ThreadMain(); });
|
||||
|
||||
std::unique_lock<std::mutex> lock(mStartupMutex);
|
||||
if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() {
|
||||
return mStarted || !mStartupError.empty();
|
||||
}))
|
||||
{
|
||||
error = "Timed out starting render thread.";
|
||||
return false;
|
||||
}
|
||||
if (!mStartupError.empty())
|
||||
{
|
||||
error = mStartupError;
|
||||
lock.unlock();
|
||||
if (mThread.joinable())
|
||||
mThread.join();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderThread::Stop()
|
||||
{
|
||||
mStopping.store(true, std::memory_order_release);
|
||||
if (mThread.joinable())
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
RenderThread::Metrics RenderThread::GetMetrics() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
return mMetrics;
|
||||
}
|
||||
|
||||
void RenderThread::ThreadMain()
|
||||
{
|
||||
HiddenGlWindow window;
|
||||
std::string error;
|
||||
if (!window.Create(mConfig.width, mConfig.height, error) || !window.MakeCurrent())
|
||||
{
|
||||
SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
|
||||
return;
|
||||
}
|
||||
if (!ResolveGLExtensions())
|
||||
{
|
||||
SignalStartupFailure("OpenGL extension resolution failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleMotionRenderer renderer;
|
||||
Bgra8ReadbackPipeline readback;
|
||||
if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
|
||||
{
|
||||
SignalStartupFailure("Render pipeline initialization failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
||||
uint64_t frameIndex = 0;
|
||||
mRunning.store(true, std::memory_order_release);
|
||||
SignalStarted();
|
||||
|
||||
while (!mStopping.load(std::memory_order_acquire))
|
||||
{
|
||||
readback.ConsumeCompleted(
|
||||
[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
|
||||
[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
|
||||
[this]() {
|
||||
CountAcquireMiss();
|
||||
},
|
||||
[this]() { CountCompleted(); });
|
||||
|
||||
const auto now = RenderCadenceClock::Clock::now();
|
||||
const RenderCadenceClock::Tick tick = clock.Poll(now);
|
||||
if (!tick.due)
|
||||
{
|
||||
if (tick.sleepFor > RenderCadenceClock::Duration::zero())
|
||||
std::this_thread::sleep_for(tick.sleepFor);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!readback.RenderAndQueue(frameIndex, [&renderer](uint64_t index) { renderer.RenderFrame(index); }))
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
++mMetrics.pboQueueMisses;
|
||||
}
|
||||
|
||||
CountRendered();
|
||||
++frameIndex;
|
||||
clock.MarkRendered(RenderCadenceClock::Clock::now());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
mMetrics.clockOverruns = clock.OverrunCount();
|
||||
mMetrics.skippedFrames = clock.SkippedFrameCount();
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < mConfig.pboDepth * 2; ++i)
|
||||
{
|
||||
readback.ConsumeCompleted(
|
||||
[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
|
||||
[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
|
||||
[this]() {
|
||||
CountAcquireMiss();
|
||||
},
|
||||
[this]() { CountCompleted(); });
|
||||
}
|
||||
|
||||
readback.Shutdown();
|
||||
renderer.ShutdownGl();
|
||||
window.ClearCurrent();
|
||||
mRunning.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void RenderThread::SignalStarted()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||
mStarted = true;
|
||||
mStartupCondition.notify_all();
|
||||
}
|
||||
|
||||
void RenderThread::SignalStartupFailure(const std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||
mStartupError = error;
|
||||
mStartupCondition.notify_all();
|
||||
}
|
||||
|
||||
void RenderThread::CountRendered()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
++mMetrics.renderedFrames;
|
||||
}
|
||||
|
||||
void RenderThread::CountCompleted()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
++mMetrics.completedReadbacks;
|
||||
}
|
||||
|
||||
void RenderThread::CountAcquireMiss()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||
++mMetrics.acquireMisses;
|
||||
}
|
||||
68
apps/RenderCadenceCompositor/render/RenderThread.h
Normal file
68
apps/RenderCadenceCompositor/render/RenderThread.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include "RenderCadenceClock.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
class SystemFrameExchange;
|
||||
|
||||
class RenderThread
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
unsigned width = 1920;
|
||||
unsigned height = 1080;
|
||||
double frameDurationMilliseconds = 1000.0 / 59.94;
|
||||
std::size_t pboDepth = 6;
|
||||
};
|
||||
|
||||
struct Metrics
|
||||
{
|
||||
uint64_t renderedFrames = 0;
|
||||
uint64_t completedReadbacks = 0;
|
||||
uint64_t acquireMisses = 0;
|
||||
uint64_t pboQueueMisses = 0;
|
||||
uint64_t clockOverruns = 0;
|
||||
uint64_t skippedFrames = 0;
|
||||
};
|
||||
|
||||
RenderThread(SystemFrameExchange& frameExchange, Config config);
|
||||
RenderThread(const RenderThread&) = delete;
|
||||
RenderThread& operator=(const RenderThread&) = delete;
|
||||
~RenderThread();
|
||||
|
||||
bool Start(std::string& error);
|
||||
void Stop();
|
||||
|
||||
Metrics GetMetrics() const;
|
||||
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
void ThreadMain();
|
||||
void SignalStarted();
|
||||
void SignalStartupFailure(const std::string& error);
|
||||
void CountRendered();
|
||||
void CountCompleted();
|
||||
void CountAcquireMiss();
|
||||
|
||||
SystemFrameExchange& mFrameExchange;
|
||||
Config mConfig;
|
||||
std::thread mThread;
|
||||
std::atomic<bool> mStopping{ false };
|
||||
std::atomic<bool> mRunning{ false };
|
||||
|
||||
mutable std::mutex mStartupMutex;
|
||||
std::condition_variable mStartupCondition;
|
||||
bool mStarted = false;
|
||||
std::string mStartupError;
|
||||
|
||||
mutable std::mutex mMetricsMutex;
|
||||
Metrics mMetrics;
|
||||
};
|
||||
50
apps/RenderCadenceCompositor/render/SimpleMotionRenderer.cpp
Normal file
50
apps/RenderCadenceCompositor/render/SimpleMotionRenderer.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "SimpleMotionRenderer.h"
|
||||
|
||||
#include "GLExtensions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
bool SimpleMotionRenderer::InitializeGl(unsigned width, unsigned height)
|
||||
{
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
return mWidth > 0 && mHeight > 0;
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::RenderFrame(uint64_t frameIndex)
|
||||
{
|
||||
const float t = static_cast<float>(frameIndex) / 60.0f;
|
||||
const float red = 0.1f + 0.4f * (0.5f + 0.5f * std::sin(t));
|
||||
const float green = 0.1f + 0.4f * (0.5f + 0.5f * std::sin(t * 0.73f + 1.0f));
|
||||
const float blue = 0.15f + 0.3f * (0.5f + 0.5f * std::sin(t * 0.41f + 2.0f));
|
||||
|
||||
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(red, green, blue, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
const int boxWidth = (std::max)(1, static_cast<int>(mWidth / 6));
|
||||
const int boxHeight = (std::max)(1, static_cast<int>(mHeight / 5));
|
||||
const float phase = 0.5f + 0.5f * std::sin(t * 1.7f);
|
||||
const int x = static_cast<int>(phase * static_cast<float>(mWidth - static_cast<unsigned>(boxWidth)));
|
||||
const int y = static_cast<int>((0.5f + 0.5f * std::sin(t * 1.1f + 0.8f)) * static_cast<float>(mHeight - static_cast<unsigned>(boxHeight)));
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(x, y, boxWidth, boxHeight);
|
||||
glClearColor(1.0f - red, 0.85f, 0.15f + blue, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
const int stripeWidth = (std::max)(1, static_cast<int>(mWidth / 80));
|
||||
const int stripeX = static_cast<int>((frameIndex % 120) * (mWidth - static_cast<unsigned>(stripeWidth)) / 119);
|
||||
glScissor(stripeX, 0, stripeWidth, static_cast<GLsizei>(mHeight));
|
||||
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::ShutdownGl()
|
||||
{
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
}
|
||||
20
apps/RenderCadenceCompositor/render/SimpleMotionRenderer.h
Normal file
20
apps/RenderCadenceCompositor/render/SimpleMotionRenderer.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class SimpleMotionRenderer
|
||||
{
|
||||
public:
|
||||
SimpleMotionRenderer() = default;
|
||||
|
||||
bool InitializeGl(unsigned width, unsigned height);
|
||||
void RenderFrame(uint64_t frameIndex);
|
||||
void ShutdownGl();
|
||||
|
||||
unsigned Width() const { return mWidth; }
|
||||
unsigned Height() const { return mHeight; }
|
||||
|
||||
private:
|
||||
unsigned mWidth = 0;
|
||||
unsigned mHeight = 0;
|
||||
};
|
||||
Reference in New Issue
Block a user