optional preview frame
This commit is contained in:
@@ -33,6 +33,12 @@ DeckLinkOutputThread
|
||||
consumes completed system-memory frames
|
||||
schedules them into DeckLink up to target depth
|
||||
never renders
|
||||
|
||||
PreviewWindowThread
|
||||
optionally owns a Win32/GDI preview window
|
||||
copies the latest completed or scheduled system-memory frame without consuming it
|
||||
skips preview ticks instead of waiting for the frame exchange lock
|
||||
never calls GL, DeckLink, shader build, or render cadence code
|
||||
```
|
||||
|
||||
Startup builds a small output preroll reserve before DeckLink scheduled playback starts. When DeckLink input is available, startup also waits briefly for three ready input frames before the render thread starts so the first render ticks are deliberate rather than lucky.
|
||||
@@ -74,6 +80,7 @@ Included now:
|
||||
- trigger parameters as latest-pulse controls with shader-visible count/time
|
||||
- startup config provider for `config/runtime-host.json`
|
||||
- quiet telemetry health monitor
|
||||
- optional preview window fed from completed system-memory frames on its own thread
|
||||
- non-GL frame-exchange tests
|
||||
- non-GL input-mailbox tests
|
||||
|
||||
@@ -87,7 +94,6 @@ Intentionally not included yet:
|
||||
- OSC control
|
||||
- persistent control/state writes
|
||||
- trigger event history for stacked repeated pulses
|
||||
- preview
|
||||
- screenshots
|
||||
- persistence
|
||||
|
||||
@@ -140,7 +146,7 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
|
||||
- [ ] Full runtime state store/read model
|
||||
- [ ] Persistent layer stack/config writes
|
||||
- [ ] OSC ingress
|
||||
- [ ] Preview output
|
||||
- [x] Preview output from a non-consuming system-memory tap
|
||||
- [ ] Screenshot capture
|
||||
- [ ] External keying support
|
||||
- [ ] Full V1 health/runtime presentation model
|
||||
@@ -198,9 +204,12 @@ Currently consumed fields:
|
||||
- `outputFrameRate`
|
||||
- `autoReload`
|
||||
- `maxTemporalHistoryFrames`
|
||||
- `previewEnabled`
|
||||
- `previewFps`
|
||||
- `enableExternalKeying`
|
||||
|
||||
When `previewEnabled` is true, the preview window runs on `PreviewWindowThread`. It paints BGRA8 system-memory frames with Win32/GDI after render readback has already completed, so it does not bind GL and does not consume frames from DeckLink output.
|
||||
|
||||
The loaded config is treated as a read-only startup snapshot. Subsystems that need config should receive this snapshot or a narrowed config struct from app orchestration; they should not reload files independently.
|
||||
|
||||
Supported CLI overrides:
|
||||
|
||||
@@ -28,7 +28,8 @@ AppConfig DefaultAppConfig()
|
||||
config.outputFrameRate = "59.94";
|
||||
config.autoReload = true;
|
||||
config.maxTemporalHistoryFrames = 12;
|
||||
config.previewFps = 30.0;
|
||||
config.previewEnabled = false;
|
||||
config.previewFps = kDefaultPreviewFps;
|
||||
config.warmupCompletedFrames = 4;
|
||||
config.warmupTimeout = std::chrono::seconds(3);
|
||||
config.prerollTimeout = std::chrono::seconds(3);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../control/http/HttpControlServer.h"
|
||||
#include "../logging/Logger.h"
|
||||
#include "../preview/PreviewConfig.h"
|
||||
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||
#include "../video/DeckLinkOutput.h"
|
||||
#include "../video/DeckLinkOutputThread.h"
|
||||
@@ -29,7 +30,8 @@ struct AppConfig
|
||||
std::string outputFrameRate = "59.94";
|
||||
bool autoReload = true;
|
||||
std::size_t maxTemporalHistoryFrames = 12;
|
||||
double previewFps = 30.0;
|
||||
bool previewEnabled = false;
|
||||
double previewFps = kDefaultPreviewFps;
|
||||
std::size_t warmupCompletedFrames = 4;
|
||||
std::chrono::milliseconds warmupTimeout = std::chrono::seconds(3);
|
||||
std::chrono::milliseconds prerollTimeout = std::chrono::seconds(3);
|
||||
|
||||
@@ -132,6 +132,7 @@ bool AppConfigProvider::Load(const std::filesystem::path& path, std::string& err
|
||||
ApplyString(root, "outputFrameRate", mConfig.outputFrameRate);
|
||||
ApplyBool(root, "autoReload", mConfig.autoReload);
|
||||
ApplySize(root, "maxTemporalHistoryFrames", mConfig.maxTemporalHistoryFrames);
|
||||
ApplyBool(root, "previewEnabled", mConfig.previewEnabled);
|
||||
ApplyDouble(root, "previewFps", mConfig.previewFps);
|
||||
ApplyBool(root, "enableExternalKeying", mConfig.deckLink.externalKeyingEnabled);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "RuntimeLayerController.h"
|
||||
#include "../logging/Logger.h"
|
||||
#include "../control/RuntimeStateJson.h"
|
||||
#include "../preview/PreviewWindowThread.h"
|
||||
#include "../telemetry/TelemetryHealthMonitor.h"
|
||||
#include "../video/DeckLinkInput.h"
|
||||
#include "../video/DeckLinkOutput.h"
|
||||
@@ -94,6 +95,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
StartPreviewWindow();
|
||||
StartOptionalVideoOutput();
|
||||
mTelemetryHealth.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||
StartHttpServer();
|
||||
@@ -106,6 +108,7 @@ public:
|
||||
{
|
||||
mHttpServer.Stop();
|
||||
mTelemetryHealth.Stop();
|
||||
mPreviewWindow.Stop();
|
||||
mOutputThread.Stop();
|
||||
mOutput.Stop();
|
||||
mRuntimeLayers.Stop();
|
||||
@@ -228,6 +231,24 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void StartPreviewWindow()
|
||||
{
|
||||
if (!mConfig.previewEnabled)
|
||||
return;
|
||||
|
||||
PreviewWindowConfig previewConfig;
|
||||
previewConfig.enabled = true;
|
||||
previewConfig.fps = mConfig.previewFps;
|
||||
std::string error;
|
||||
if (mPreviewWindow.Start(mFrameExchange, previewConfig, error))
|
||||
{
|
||||
Log("preview", "Preview window thread started.");
|
||||
return;
|
||||
}
|
||||
|
||||
LogWarning("preview", "Preview window did not start: " + error);
|
||||
}
|
||||
|
||||
std::string BuildStateJson()
|
||||
{
|
||||
CadenceTelemetrySnapshot telemetry = mHttpTelemetry.Sample(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||
@@ -281,6 +302,7 @@ private:
|
||||
TelemetryHealthMonitor mTelemetryHealth;
|
||||
CadenceTelemetry mHttpTelemetry;
|
||||
HttpControlServer mHttpServer;
|
||||
PreviewWindowThread mPreviewWindow;
|
||||
RuntimeLayerController mRuntimeLayers;
|
||||
std::function<DeckLinkInputMetrics()> mDeckLinkInputMetricsProvider;
|
||||
uint64_t mLastInputCapturedFrames = 0;
|
||||
|
||||
@@ -29,6 +29,7 @@ void SystemFrameExchange::Configure(const SystemFrameExchangeConfig& config)
|
||||
slot.bytes.resize(byteCount);
|
||||
slot.state = SystemFrameSlotState::Free;
|
||||
slot.frameIndex = 0;
|
||||
slot.previewReaders = 0;
|
||||
++slot.generation;
|
||||
}
|
||||
|
||||
@@ -110,16 +111,65 @@ bool SystemFrameExchange::ReleaseScheduledByBytes(void* bytes)
|
||||
if (slot.state != SystemFrameSlotState::Scheduled)
|
||||
return false;
|
||||
|
||||
slot.state = SystemFrameSlotState::Free;
|
||||
slot.frameIndex = 0;
|
||||
++slot.generation;
|
||||
mCondition.notify_all();
|
||||
ReleaseFreeIfUnreferencedLocked(slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemFrameExchange::TryAcquireLatestForPreview(SystemFrame& frame)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
std::size_t bestIndex = mSlots.size();
|
||||
uint64_t bestFrameIndex = 0;
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
const Slot& slot = mSlots[index];
|
||||
if (slot.state != SystemFrameSlotState::Completed && slot.state != SystemFrameSlotState::Scheduled)
|
||||
continue;
|
||||
if (bestIndex == mSlots.size() || slot.frameIndex >= bestFrameIndex)
|
||||
{
|
||||
bestIndex = index;
|
||||
bestFrameIndex = slot.frameIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestIndex == mSlots.size())
|
||||
{
|
||||
frame = SystemFrame();
|
||||
return false;
|
||||
}
|
||||
|
||||
Slot& slot = mSlots[bestIndex];
|
||||
++slot.previewReaders;
|
||||
FillFrameLocked(bestIndex, frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemFrameExchange::ReleasePreviewFrame(const SystemFrame& frame)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!IsValidLocked(frame))
|
||||
return false;
|
||||
|
||||
Slot& slot = mSlots[frame.index];
|
||||
if (slot.previewReaders == 0)
|
||||
return false;
|
||||
|
||||
--slot.previewReaders;
|
||||
if (slot.previewReaders == 0 && slot.state == SystemFrameSlotState::Free)
|
||||
{
|
||||
slot.frameIndex = 0;
|
||||
++slot.generation;
|
||||
mCondition.notify_all();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemFrameExchange::WaitForCompletedDepth(std::size_t targetDepth, std::chrono::milliseconds timeout)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
@@ -180,8 +230,11 @@ void SystemFrameExchange::Clear()
|
||||
for (Slot& slot : mSlots)
|
||||
{
|
||||
slot.state = SystemFrameSlotState::Free;
|
||||
slot.frameIndex = 0;
|
||||
++slot.generation;
|
||||
if (slot.previewReaders == 0)
|
||||
{
|
||||
slot.frameIndex = 0;
|
||||
++slot.generation;
|
||||
}
|
||||
}
|
||||
mCondition.notify_all();
|
||||
}
|
||||
@@ -198,7 +251,8 @@ SystemFrameExchangeMetrics SystemFrameExchange::Metrics() const
|
||||
switch (slot.state)
|
||||
{
|
||||
case SystemFrameSlotState::Free:
|
||||
++metrics.freeCount;
|
||||
if (slot.previewReaders == 0)
|
||||
++metrics.freeCount;
|
||||
break;
|
||||
case SystemFrameSlotState::Rendering:
|
||||
++metrics.renderingCount;
|
||||
@@ -222,6 +276,8 @@ bool SystemFrameExchange::AcquireFreeLocked(SystemFrame& frame)
|
||||
Slot& slot = mSlots[index];
|
||||
if (slot.state != SystemFrameSlotState::Free)
|
||||
continue;
|
||||
if (slot.previewReaders != 0)
|
||||
continue;
|
||||
|
||||
slot.state = SystemFrameSlotState::Rendering;
|
||||
++slot.generation;
|
||||
@@ -242,17 +298,26 @@ bool SystemFrameExchange::DropOldestCompletedLocked()
|
||||
continue;
|
||||
|
||||
Slot& slot = mSlots[index];
|
||||
slot.state = SystemFrameSlotState::Free;
|
||||
slot.frameIndex = 0;
|
||||
++slot.generation;
|
||||
ReleaseFreeIfUnreferencedLocked(slot);
|
||||
++mCounters.completedDrops;
|
||||
mCondition.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SystemFrameExchange::ReleaseFreeIfUnreferencedLocked(Slot& slot)
|
||||
{
|
||||
slot.state = SystemFrameSlotState::Free;
|
||||
if (slot.previewReaders != 0)
|
||||
return false;
|
||||
|
||||
slot.frameIndex = 0;
|
||||
++slot.generation;
|
||||
mCondition.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SystemFrameExchange::TrimCompletedLocked()
|
||||
{
|
||||
if (mConfig.maxCompletedFrames == 0)
|
||||
|
||||
@@ -21,6 +21,8 @@ public:
|
||||
bool PublishCompleted(const SystemFrame& frame);
|
||||
bool ConsumeCompletedForSchedule(SystemFrame& frame);
|
||||
bool ReleaseScheduledByBytes(void* bytes);
|
||||
bool TryAcquireLatestForPreview(SystemFrame& frame);
|
||||
bool ReleasePreviewFrame(const SystemFrame& frame);
|
||||
bool WaitForCompletedDepth(std::size_t targetDepth, std::chrono::milliseconds timeout);
|
||||
bool WaitForStableCompletedDepth(
|
||||
std::size_t targetDepth,
|
||||
@@ -37,10 +39,12 @@ private:
|
||||
SystemFrameSlotState state = SystemFrameSlotState::Free;
|
||||
uint64_t generation = 1;
|
||||
uint64_t frameIndex = 0;
|
||||
std::size_t previewReaders = 0;
|
||||
};
|
||||
|
||||
bool AcquireFreeLocked(SystemFrame& frame);
|
||||
bool DropOldestCompletedLocked();
|
||||
bool ReleaseFreeIfUnreferencedLocked(Slot& slot);
|
||||
void TrimCompletedLocked();
|
||||
bool IsValidLocked(const SystemFrame& frame) const;
|
||||
void FillFrameLocked(std::size_t index, SystemFrame& frame);
|
||||
|
||||
28
src/preview/PreviewConfig.h
Normal file
28
src/preview/PreviewConfig.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
constexpr double kDefaultPreviewFps = 30.0;
|
||||
constexpr double kMinimumPreviewFps = 1.0;
|
||||
|
||||
struct PreviewWindowConfig
|
||||
{
|
||||
bool enabled = false;
|
||||
double fps = kDefaultPreviewFps;
|
||||
std::string title = "Render Cadence Preview";
|
||||
};
|
||||
|
||||
inline double NormalizePreviewFps(double fps)
|
||||
{
|
||||
return fps >= kMinimumPreviewFps ? fps : kDefaultPreviewFps;
|
||||
}
|
||||
|
||||
inline unsigned PreviewTimerIntervalMilliseconds(double fps)
|
||||
{
|
||||
const double normalizedFps = NormalizePreviewFps(fps);
|
||||
const int intervalMilliseconds = static_cast<int>(1000.0 / normalizedFps);
|
||||
return static_cast<unsigned>(intervalMilliseconds > 0 ? intervalMilliseconds : 1);
|
||||
}
|
||||
}
|
||||
246
src/preview/PreviewWindowThread.cpp
Normal file
246
src/preview/PreviewWindowThread.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "PreviewWindowThread.h"
|
||||
|
||||
#include "../frames/SystemFrameExchange.h"
|
||||
#include "../logging/Logger.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char* kPreviewWindowClassName = "RenderCadencePreviewWindow";
|
||||
constexpr UINT_PTR kPreviewTimerId = 1;
|
||||
constexpr UINT kPreviewStopMessage = WM_APP + 1;
|
||||
|
||||
void RegisterPreviewWindowClass()
|
||||
{
|
||||
static bool registered = false;
|
||||
if (registered)
|
||||
return;
|
||||
|
||||
WNDCLASSA windowClass = {};
|
||||
windowClass.lpfnWndProc = PreviewWindowThread::WindowProc;
|
||||
windowClass.hInstance = GetModuleHandleA(nullptr);
|
||||
windowClass.lpszClassName = kPreviewWindowClassName;
|
||||
windowClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
windowClass.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
|
||||
RegisterClassA(&windowClass);
|
||||
registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
PreviewWindowThread::~PreviewWindowThread()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool PreviewWindowThread::Start(SystemFrameExchange& exchange, const PreviewWindowConfig& config, std::string& error)
|
||||
{
|
||||
if (!config.enabled)
|
||||
return true;
|
||||
if (mThread.joinable())
|
||||
{
|
||||
error = "Preview window thread is already running.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mExchange = &exchange;
|
||||
mConfig = config;
|
||||
mStopRequested.store(false, std::memory_order_release);
|
||||
mThread = std::thread(&PreviewWindowThread::ThreadMain, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreviewWindowThread::Stop()
|
||||
{
|
||||
mStopRequested.store(true, std::memory_order_release);
|
||||
const DWORD threadId = mThreadId.load(std::memory_order_acquire);
|
||||
if (threadId != 0)
|
||||
PostThreadMessageA(threadId, kPreviewStopMessage, 0, 0);
|
||||
if (mThread.joinable())
|
||||
mThread.join();
|
||||
mExchange = nullptr;
|
||||
mRunning.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK PreviewWindowThread::WindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
PreviewWindowThread* owner = reinterpret_cast<PreviewWindowThread*>(GetWindowLongPtrA(window, GWLP_USERDATA));
|
||||
if (message == WM_NCCREATE)
|
||||
{
|
||||
const CREATESTRUCTA* create = reinterpret_cast<const CREATESTRUCTA*>(lParam);
|
||||
owner = reinterpret_cast<PreviewWindowThread*>(create->lpCreateParams);
|
||||
SetWindowLongPtrA(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(owner));
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_PAINT:
|
||||
if (owner != nullptr)
|
||||
{
|
||||
owner->Paint(window);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_TIMER:
|
||||
if (owner != nullptr && wParam == kPreviewTimerId)
|
||||
{
|
||||
InvalidateRect(window, nullptr, FALSE);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_CLOSE:
|
||||
if (owner != nullptr)
|
||||
{
|
||||
if (!owner->mStopRequested.load(std::memory_order_acquire))
|
||||
TryLog(LogLevel::Log, "preview", "Preview window closed by user.");
|
||||
owner->mStopRequested.store(true, std::memory_order_release);
|
||||
}
|
||||
DestroyWindow(window);
|
||||
return 0;
|
||||
case WM_DESTROY:
|
||||
if (owner != nullptr)
|
||||
owner->mWindow = nullptr;
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return DefWindowProcA(window, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void PreviewWindowThread::ThreadMain()
|
||||
{
|
||||
MSG message = {};
|
||||
PeekMessageA(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
|
||||
mThreadId.store(GetCurrentThreadId(), std::memory_order_release);
|
||||
mRunning.store(true, std::memory_order_release);
|
||||
|
||||
std::string error;
|
||||
if (!CreatePreviewWindow(error))
|
||||
{
|
||||
TryLog(LogLevel::Error, "preview", error.empty() ? "Preview window creation failed." : error);
|
||||
mThreadId.store(0, std::memory_order_release);
|
||||
mRunning.store(false, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mStopRequested.load(std::memory_order_acquire))
|
||||
{
|
||||
if (SetTimer(mWindow, kPreviewTimerId, PreviewTimerIntervalMilliseconds(mConfig.fps), nullptr) == 0)
|
||||
TryLog(LogLevel::Error, "preview", "Preview window timer could not be started.");
|
||||
else
|
||||
TryLog(LogLevel::Log, "preview", "Preview window started.");
|
||||
}
|
||||
|
||||
while (!mStopRequested.load(std::memory_order_acquire))
|
||||
{
|
||||
const BOOL result = GetMessageA(&message, nullptr, 0, 0);
|
||||
if (result <= 0)
|
||||
break;
|
||||
if (message.message == kPreviewStopMessage)
|
||||
{
|
||||
mStopRequested.store(true, std::memory_order_release);
|
||||
break;
|
||||
}
|
||||
TranslateMessage(&message);
|
||||
DispatchMessageA(&message);
|
||||
}
|
||||
|
||||
if (mWindow != nullptr)
|
||||
{
|
||||
KillTimer(mWindow, kPreviewTimerId);
|
||||
DestroyWindow(mWindow);
|
||||
mWindow = nullptr;
|
||||
}
|
||||
|
||||
mThreadId.store(0, std::memory_order_release);
|
||||
mRunning.store(false, std::memory_order_release);
|
||||
TryLog(LogLevel::Log, "preview", "Preview window thread stopped.");
|
||||
}
|
||||
|
||||
bool PreviewWindowThread::CreatePreviewWindow(std::string& error)
|
||||
{
|
||||
RegisterPreviewWindowClass();
|
||||
|
||||
mWindow = CreateWindowExA(
|
||||
0,
|
||||
kPreviewWindowClassName,
|
||||
mConfig.title.c_str(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
960,
|
||||
540,
|
||||
nullptr,
|
||||
nullptr,
|
||||
GetModuleHandleA(nullptr),
|
||||
this);
|
||||
|
||||
if (mWindow == nullptr)
|
||||
{
|
||||
error = "CreateWindowEx failed for preview window.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ShowWindow(mWindow, SW_SHOW);
|
||||
UpdateWindow(mWindow);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreviewWindowThread::Paint(HWND window)
|
||||
{
|
||||
PAINTSTRUCT paint = {};
|
||||
HDC dc = BeginPaint(window, &paint);
|
||||
|
||||
RECT client = {};
|
||||
GetClientRect(window, &client);
|
||||
const int clientWidth = std::max(1L, client.right - client.left);
|
||||
const int clientHeight = std::max(1L, client.bottom - client.top);
|
||||
|
||||
SystemFrame frame;
|
||||
const bool frameAcquired = mExchange != nullptr && mExchange->TryAcquireLatestForPreview(frame);
|
||||
const bool canPaintFrame =
|
||||
frameAcquired &&
|
||||
frame.bytes != nullptr &&
|
||||
frame.pixelFormat == VideoIOPixelFormat::Bgra8 &&
|
||||
frame.width > 0 &&
|
||||
frame.height > 0;
|
||||
if (canPaintFrame)
|
||||
{
|
||||
BITMAPINFO bitmapInfo = {};
|
||||
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bitmapInfo.bmiHeader.biWidth = static_cast<LONG>(frame.width);
|
||||
bitmapInfo.bmiHeader.biHeight = -static_cast<LONG>(frame.height);
|
||||
bitmapInfo.bmiHeader.biPlanes = 1;
|
||||
bitmapInfo.bmiHeader.biBitCount = 32;
|
||||
bitmapInfo.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
StretchDIBits(
|
||||
dc,
|
||||
0,
|
||||
0,
|
||||
clientWidth,
|
||||
clientHeight,
|
||||
0,
|
||||
0,
|
||||
static_cast<int>(frame.width),
|
||||
static_cast<int>(frame.height),
|
||||
frame.bytes,
|
||||
&bitmapInfo,
|
||||
DIB_RGB_COLORS,
|
||||
SRCCOPY);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillRect(dc, &client, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
|
||||
}
|
||||
|
||||
if (frameAcquired)
|
||||
mExchange->ReleasePreviewFrame(frame);
|
||||
|
||||
EndPaint(window, &paint);
|
||||
}
|
||||
}
|
||||
47
src/preview/PreviewWindowThread.h
Normal file
47
src/preview/PreviewWindowThread.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "PreviewConfig.h"
|
||||
|
||||
#include "../frames/SystemFrameTypes.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
class SystemFrameExchange;
|
||||
|
||||
namespace RenderCadenceCompositor
|
||||
{
|
||||
class PreviewWindowThread
|
||||
{
|
||||
public:
|
||||
PreviewWindowThread() = default;
|
||||
PreviewWindowThread(const PreviewWindowThread&) = delete;
|
||||
PreviewWindowThread& operator=(const PreviewWindowThread&) = delete;
|
||||
~PreviewWindowThread();
|
||||
|
||||
bool Start(SystemFrameExchange& exchange, const PreviewWindowConfig& config, std::string& error);
|
||||
void Stop();
|
||||
bool Running() const { return mRunning.load(std::memory_order_acquire); }
|
||||
|
||||
static LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
private:
|
||||
void ThreadMain();
|
||||
bool CreatePreviewWindow(std::string& error);
|
||||
void Paint(HWND window);
|
||||
|
||||
SystemFrameExchange* mExchange = nullptr;
|
||||
PreviewWindowConfig mConfig;
|
||||
std::thread mThread;
|
||||
std::atomic<bool> mStopRequested{ false };
|
||||
std::atomic<bool> mRunning{ false };
|
||||
std::atomic<DWORD> mThreadId{ 0 };
|
||||
HWND mWindow = nullptr;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user