optional preview frame
This commit is contained in:
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