Compare commits
2 Commits
bfaa3f5e0e
...
f589b1e1fe
| Author | SHA1 | Date | |
|---|---|---|---|
| f589b1e1fe | |||
| 7e17315e74 |
@@ -11,6 +11,6 @@
|
|||||||
"autoReload": true,
|
"autoReload": true,
|
||||||
"maxTemporalHistoryFrames": 12,
|
"maxTemporalHistoryFrames": 12,
|
||||||
"previewEnabled": true,
|
"previewEnabled": true,
|
||||||
"previewFps": 30,
|
"previewFps": 59.94,
|
||||||
"enableExternalKeying": true
|
"enableExternalKeying": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ Currently consumed fields:
|
|||||||
- `previewFps`
|
- `previewFps`
|
||||||
- `enableExternalKeying`
|
- `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.
|
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. `previewFps` controls the preview repaint cadence; the default is 60 fps and `config/runtime-host.json` tracks the shipped 59.94 output cadence.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace RenderCadenceCompositor
|
namespace RenderCadenceCompositor
|
||||||
{
|
{
|
||||||
constexpr double kDefaultPreviewFps = 30.0;
|
constexpr double kDefaultPreviewFps = 60.0;
|
||||||
constexpr double kMinimumPreviewFps = 1.0;
|
constexpr double kMinimumPreviewFps = 1.0;
|
||||||
|
|
||||||
struct PreviewWindowConfig
|
struct PreviewWindowConfig
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "../logging/Logger.h"
|
#include "../logging/Logger.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace RenderCadenceCompositor
|
namespace RenderCadenceCompositor
|
||||||
{
|
{
|
||||||
@@ -210,28 +211,48 @@ void PreviewWindowThread::Paint(HWND window)
|
|||||||
frame.height > 0;
|
frame.height > 0;
|
||||||
if (canPaintFrame)
|
if (canPaintFrame)
|
||||||
{
|
{
|
||||||
BITMAPINFO bitmapInfo = {};
|
if (CopyPreviewOrientedBgra8Frame(frame))
|
||||||
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
{
|
||||||
bitmapInfo.bmiHeader.biWidth = static_cast<LONG>(frame.width);
|
const int previousStretchMode = SetStretchBltMode(dc, HALFTONE);
|
||||||
bitmapInfo.bmiHeader.biHeight = -static_cast<LONG>(frame.height);
|
POINT previousBrushOrigin = {};
|
||||||
bitmapInfo.bmiHeader.biPlanes = 1;
|
SetBrushOrgEx(dc, 0, 0, &previousBrushOrigin);
|
||||||
bitmapInfo.bmiHeader.biBitCount = 32;
|
|
||||||
bitmapInfo.bmiHeader.biCompression = BI_RGB;
|
|
||||||
|
|
||||||
StretchDIBits(
|
BITMAPINFO bitmapInfo = {};
|
||||||
dc,
|
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
0,
|
bitmapInfo.bmiHeader.biWidth = static_cast<LONG>(frame.width);
|
||||||
0,
|
bitmapInfo.bmiHeader.biHeight = -static_cast<LONG>(frame.height);
|
||||||
clientWidth,
|
bitmapInfo.bmiHeader.biPlanes = 1;
|
||||||
clientHeight,
|
bitmapInfo.bmiHeader.biBitCount = 32;
|
||||||
0,
|
bitmapInfo.bmiHeader.biCompression = BI_RGB;
|
||||||
0,
|
|
||||||
static_cast<int>(frame.width),
|
StretchDIBits(
|
||||||
static_cast<int>(frame.height),
|
dc,
|
||||||
frame.bytes,
|
0,
|
||||||
&bitmapInfo,
|
0,
|
||||||
DIB_RGB_COLORS,
|
clientWidth,
|
||||||
SRCCOPY);
|
clientHeight,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
static_cast<int>(frame.width),
|
||||||
|
static_cast<int>(frame.height),
|
||||||
|
mPaintPixels.data(),
|
||||||
|
&bitmapInfo,
|
||||||
|
DIB_RGB_COLORS,
|
||||||
|
SRCCOPY);
|
||||||
|
|
||||||
|
if (previousStretchMode != 0)
|
||||||
|
SetStretchBltMode(dc, previousStretchMode);
|
||||||
|
SetBrushOrgEx(dc, previousBrushOrigin.x, previousBrushOrigin.y, nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!mLoggedPaintCopyFailure)
|
||||||
|
{
|
||||||
|
TryLog(LogLevel::Warning, "preview", "Preview frame could not be copied for mirrored presentation.");
|
||||||
|
mLoggedPaintCopyFailure = true;
|
||||||
|
}
|
||||||
|
FillRect(dc, &client, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -243,4 +264,34 @@ void PreviewWindowThread::Paint(HWND window)
|
|||||||
|
|
||||||
EndPaint(window, &paint);
|
EndPaint(window, &paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PreviewWindowThread::CopyPreviewOrientedBgra8Frame(const SystemFrame& frame)
|
||||||
|
{
|
||||||
|
constexpr std::size_t kBgraBytesPerPixel = 4;
|
||||||
|
if (frame.bytes == nullptr || frame.width == 0 || frame.height == 0 || frame.rowBytes <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const std::size_t width = static_cast<std::size_t>(frame.width);
|
||||||
|
const std::size_t height = static_cast<std::size_t>(frame.height);
|
||||||
|
const std::size_t sourceRowBytes = static_cast<std::size_t>(frame.rowBytes);
|
||||||
|
const std::size_t destinationRowBytes = width * kBgraBytesPerPixel;
|
||||||
|
if (sourceRowBytes < destinationRowBytes)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
mPaintPixels.resize(destinationRowBytes * height);
|
||||||
|
const unsigned char* sourceBytes = static_cast<const unsigned char*>(frame.bytes);
|
||||||
|
for (std::size_t y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
const unsigned char* sourceRow = sourceBytes + (height - 1u - y) * sourceRowBytes;
|
||||||
|
unsigned char* destinationRow = mPaintPixels.data() + y * destinationRowBytes;
|
||||||
|
for (std::size_t x = 0; x < width; ++x)
|
||||||
|
{
|
||||||
|
const unsigned char* sourcePixel = sourceRow + x * kBgraBytesPerPixel;
|
||||||
|
unsigned char* destinationPixel = destinationRow + x * kBgraBytesPerPixel;
|
||||||
|
std::memcpy(destinationPixel, sourcePixel, kBgraBytesPerPixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
@@ -35,9 +36,12 @@ private:
|
|||||||
void ThreadMain();
|
void ThreadMain();
|
||||||
bool CreatePreviewWindow(std::string& error);
|
bool CreatePreviewWindow(std::string& error);
|
||||||
void Paint(HWND window);
|
void Paint(HWND window);
|
||||||
|
bool CopyPreviewOrientedBgra8Frame(const SystemFrame& frame);
|
||||||
|
|
||||||
SystemFrameExchange* mExchange = nullptr;
|
SystemFrameExchange* mExchange = nullptr;
|
||||||
PreviewWindowConfig mConfig;
|
PreviewWindowConfig mConfig;
|
||||||
|
std::vector<unsigned char> mPaintPixels;
|
||||||
|
bool mLoggedPaintCopyFailure = false;
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
std::atomic<bool> mStopRequested{ false };
|
std::atomic<bool> mStopRequested{ false };
|
||||||
std::atomic<bool> mRunning{ false };
|
std::atomic<bool> mRunning{ false };
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ void TestPreviewDefaultsAreOptIn()
|
|||||||
|
|
||||||
const AppConfig config = DefaultAppConfig();
|
const AppConfig config = DefaultAppConfig();
|
||||||
Expect(!config.previewEnabled, "preview is disabled by default");
|
Expect(!config.previewEnabled, "preview is disabled by default");
|
||||||
Expect(config.previewFps == 30.0, "preview fps default is 30");
|
Expect(config.previewFps == 60.0, "preview fps default is 60");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestHelpers()
|
void TestHelpers()
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ void TestTimerIntervalUsesConfiguredFps()
|
|||||||
{
|
{
|
||||||
Expect(RenderCadenceCompositor::PreviewTimerIntervalMilliseconds(25.0) == 40, "25 fps maps to 40 ms");
|
Expect(RenderCadenceCompositor::PreviewTimerIntervalMilliseconds(25.0) == 40, "25 fps maps to 40 ms");
|
||||||
Expect(RenderCadenceCompositor::PreviewTimerIntervalMilliseconds(50.0) == 20, "50 fps maps to 20 ms");
|
Expect(RenderCadenceCompositor::PreviewTimerIntervalMilliseconds(50.0) == 20, "50 fps maps to 20 ms");
|
||||||
|
Expect(RenderCadenceCompositor::PreviewTimerIntervalMilliseconds(59.94) == 16, "59.94 fps maps to 16 ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestInvalidFpsUsesDefault()
|
void TestInvalidFpsUsesDefault()
|
||||||
|
|||||||
Reference in New Issue
Block a user