Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7d0785c66 | |||
| 8ef858e3c8 | |||
| bccf6cfb10 | |||
| e4724395f0 |
+108
-6
@@ -15,6 +15,9 @@
|
|||||||
std::optional<PresentModeConfig> parsePresentMode(
|
std::optional<PresentModeConfig> parsePresentMode(
|
||||||
std::string_view text);
|
std::string_view text);
|
||||||
|
|
||||||
|
std::optional<V210StagingMemoryMode> parseV210StagingMemoryMode(
|
||||||
|
std::string_view text);
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
struct JsonValue
|
struct JsonValue
|
||||||
@@ -574,6 +577,14 @@ bool applyConfigFile(
|
|||||||
{
|
{
|
||||||
config.paceUploads = boolValue;
|
config.paceUploads = boolValue;
|
||||||
}
|
}
|
||||||
|
if (jsonBool(objectField(*root, "v210UploadWorker"), boolValue))
|
||||||
|
{
|
||||||
|
config.v210UploadWorker = boolValue;
|
||||||
|
}
|
||||||
|
if (jsonBool(objectField(*root, "adaptiveFeedTexture"), boolValue))
|
||||||
|
{
|
||||||
|
config.adaptiveFeedTexture = boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (const std::string* present =
|
if (const std::string* present =
|
||||||
jsonString(objectField(*root, "present")))
|
jsonString(objectField(*root, "present")))
|
||||||
@@ -588,6 +599,20 @@ bool applyConfigFile(
|
|||||||
config.presentMode = mode.value();
|
config.presentMode = mode.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const std::string* memoryMode =
|
||||||
|
jsonString(objectField(*root, "v210StagingMemory")))
|
||||||
|
{
|
||||||
|
const std::optional<V210StagingMemoryMode> mode =
|
||||||
|
parseV210StagingMemoryMode(*memoryMode);
|
||||||
|
if (!mode.has_value())
|
||||||
|
{
|
||||||
|
error = "v210StagingMemory must be default, cached, "
|
||||||
|
"or device-local";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
config.v210StagingMemoryMode = mode.value();
|
||||||
|
}
|
||||||
|
|
||||||
const JsonValue::Array* feeds =
|
const JsonValue::Array* feeds =
|
||||||
jsonArray(objectField(*root, "feeds"));
|
jsonArray(objectField(*root, "feeds"));
|
||||||
if (feeds == nullptr)
|
if (feeds == nullptr)
|
||||||
@@ -660,12 +685,6 @@ bool applyConfigFile(
|
|||||||
feed.kind = FeedKind::MxlSdk;
|
feed.kind = FeedKind::MxlSdk;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const std::string* label =
|
|
||||||
jsonString(objectField(*feedObject, "label")))
|
|
||||||
{
|
|
||||||
feed.label = *label;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.feeds[i] = std::move(feed);
|
config.feeds[i] = std::move(feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,6 +746,27 @@ std::optional<PresentModeConfig> parsePresentMode(std::string_view text)
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<V210StagingMemoryMode> parseV210StagingMemoryMode(
|
||||||
|
std::string_view text)
|
||||||
|
{
|
||||||
|
if (text == "default")
|
||||||
|
{
|
||||||
|
return V210StagingMemoryMode::Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text == "cached")
|
||||||
|
{
|
||||||
|
return V210StagingMemoryMode::Cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text == "device-local" || text == "device_local")
|
||||||
|
{
|
||||||
|
return V210StagingMemoryMode::DeviceLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
std::string_view feedKindName(FeedKind kind)
|
std::string_view feedKindName(FeedKind kind)
|
||||||
{
|
{
|
||||||
switch (kind)
|
switch (kind)
|
||||||
@@ -749,6 +789,23 @@ std::string_view feedKindName(FeedKind kind)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view v210StagingMemoryModeName(
|
||||||
|
V210StagingMemoryMode mode)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case V210StagingMemoryMode::Cached:
|
||||||
|
return "cached";
|
||||||
|
|
||||||
|
case V210StagingMemoryMode::DeviceLocal:
|
||||||
|
return "device-local";
|
||||||
|
|
||||||
|
case V210StagingMemoryMode::Default:
|
||||||
|
default:
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void printUsage(const char* executableName)
|
void printUsage(const char* executableName)
|
||||||
{
|
{
|
||||||
std::cout
|
std::cout
|
||||||
@@ -760,10 +817,13 @@ void printUsage(const char* executableName)
|
|||||||
<< " --perf Log per-frame CPU timing breakdown\n"
|
<< " --perf Log per-frame CPU timing breakdown\n"
|
||||||
<< " --verbose Enable detailed startup and SDK logs\n"
|
<< " --verbose Enable detailed startup and SDK logs\n"
|
||||||
<< " --pace-uploads Pace v210 uploads by feed frame rate\n"
|
<< " --pace-uploads Pace v210 uploads by feed frame rate\n"
|
||||||
|
<< " --v210-upload-worker Copy direct v210 payloads on a worker thread\n"
|
||||||
|
<< " --adaptive-feed-texture Size feed textures to grid tile size\n"
|
||||||
<< " --config <path> Load JSON feed configuration\n"
|
<< " --config <path> Load JSON feed configuration\n"
|
||||||
<< " --grid <cols>x<rows> Set multiview grid (default 2x2, max 16 feeds)\n"
|
<< " --grid <cols>x<rows> Set multiview grid (default 2x2, max 16 feeds)\n"
|
||||||
<< " --fps-cap <fps> Limit render loop FPS (default 60)\n"
|
<< " --fps-cap <fps> Limit render loop FPS (default 60)\n"
|
||||||
<< " --max-v210-uploads <n> Limit v210 uploads per frame (0 = unlimited)\n"
|
<< " --max-v210-uploads <n> Limit v210 uploads per frame (0 = unlimited)\n"
|
||||||
|
<< " --v210-staging-memory <mode> Memory: default, cached, device-local\n"
|
||||||
<< " --present <mode> Present mode: fifo, mailbox, immediate (default mailbox)\n"
|
<< " --present <mode> Present mode: fifo, mailbox, immediate (default mailbox)\n"
|
||||||
<< " --feed <idx> <kind> Set feed slot kind explicitly\n"
|
<< " --feed <idx> <kind> Set feed slot kind explicitly\n"
|
||||||
<< " --domain <idx> <path> Set MXL domain for feed slot\n"
|
<< " --domain <idx> <path> Set MXL domain for feed slot\n"
|
||||||
@@ -905,6 +965,48 @@ ConfigParseResult parseAppConfig(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg == "--v210-upload-worker")
|
||||||
|
{
|
||||||
|
result.config.v210UploadWorker = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg == "--adaptive-feed-texture")
|
||||||
|
{
|
||||||
|
result.config.adaptiveFeedTexture = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg == "--v210-staging-memory")
|
||||||
|
{
|
||||||
|
if (i + 1 >= argc)
|
||||||
|
{
|
||||||
|
std::cerr << "--v210-staging-memory requires <mode>"
|
||||||
|
<< std::endl;
|
||||||
|
result.shouldExit = true;
|
||||||
|
result.exitCode = 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<V210StagingMemoryMode> mode =
|
||||||
|
parseV210StagingMemoryMode(argv[i + 1]);
|
||||||
|
|
||||||
|
if (!mode.has_value())
|
||||||
|
{
|
||||||
|
std::cerr
|
||||||
|
<< "--v210-staging-memory must be default, cached, "
|
||||||
|
<< "or device-local"
|
||||||
|
<< std::endl;
|
||||||
|
result.shouldExit = true;
|
||||||
|
result.exitCode = 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.config.v210StagingMemoryMode = mode.value();
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (arg == "--grid")
|
if (arg == "--grid")
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
|
|||||||
+13
-1
@@ -30,12 +30,18 @@ enum class PresentModeConfig
|
|||||||
Immediate
|
Immediate
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class V210StagingMemoryMode
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Cached,
|
||||||
|
DeviceLocal
|
||||||
|
};
|
||||||
|
|
||||||
struct FeedConfig
|
struct FeedConfig
|
||||||
{
|
{
|
||||||
FeedKind kind = FeedKind::NoSignal;
|
FeedKind kind = FeedKind::NoSignal;
|
||||||
std::string mxlDomain;
|
std::string mxlDomain;
|
||||||
std::string mxlFlowId;
|
std::string mxlFlowId;
|
||||||
std::string label;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AppConfig
|
struct AppConfig
|
||||||
@@ -44,16 +50,22 @@ struct AppConfig
|
|||||||
bool logPerf = false;
|
bool logPerf = false;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
bool paceUploads = false;
|
bool paceUploads = false;
|
||||||
|
bool v210UploadWorker = false;
|
||||||
|
bool adaptiveFeedTexture = false;
|
||||||
uint32_t fpsCap = 60;
|
uint32_t fpsCap = 60;
|
||||||
uint32_t maxV210UploadsPerFrame = 0;
|
uint32_t maxV210UploadsPerFrame = 0;
|
||||||
uint32_t gridCols = DefaultGridCols;
|
uint32_t gridCols = DefaultGridCols;
|
||||||
uint32_t gridRows = DefaultGridRows;
|
uint32_t gridRows = DefaultGridRows;
|
||||||
PresentModeConfig presentMode = PresentModeConfig::Mailbox;
|
PresentModeConfig presentMode = PresentModeConfig::Mailbox;
|
||||||
|
V210StagingMemoryMode v210StagingMemoryMode =
|
||||||
|
V210StagingMemoryMode::Default;
|
||||||
std::vector<FeedConfig> feeds;
|
std::vector<FeedConfig> feeds;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<FeedKind> parseFeedKind(std::string_view text);
|
std::optional<FeedKind> parseFeedKind(std::string_view text);
|
||||||
std::string_view feedKindName(FeedKind kind);
|
std::string_view feedKindName(FeedKind kind);
|
||||||
|
std::string_view v210StagingMemoryModeName(
|
||||||
|
V210StagingMemoryMode mode);
|
||||||
|
|
||||||
void printUsage(const char* executableName);
|
void printUsage(const char* executableName);
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -160,10 +160,10 @@ namespace
|
|||||||
{
|
{
|
||||||
const char* keys[] =
|
const char* keys[] =
|
||||||
{
|
{
|
||||||
"label",
|
"name",
|
||||||
"description",
|
|
||||||
"flow_name",
|
"flow_name",
|
||||||
"name"
|
"description",
|
||||||
|
"label"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const char* key : keys)
|
for (const char* key : keys)
|
||||||
|
|||||||
+1
-5
@@ -95,8 +95,6 @@ inline void decodeV210Line(
|
|||||||
default: y10 = y0; cb10 = cb0; cr10 = cr0; break;
|
default: y10 = y0; cb10 = cb0; cr10 = cr0; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BT.709 limited-range YCbCr to RGB. v210 stores 10-bit video-range
|
|
||||||
// samples: Y [64, 940], Cb/Cr centered at 512.
|
|
||||||
int32_t yp = (static_cast<int32_t>(y10) - 64) >> 2;
|
int32_t yp = (static_cast<int32_t>(y10) - 64) >> 2;
|
||||||
int32_t cbp = (static_cast<int32_t>(cb10) - 512) >> 2;
|
int32_t cbp = (static_cast<int32_t>(cb10) - 512) >> 2;
|
||||||
int32_t crp = (static_cast<int32_t>(cr10) - 512) >> 2;
|
int32_t crp = (static_cast<int32_t>(cr10) - 512) >> 2;
|
||||||
@@ -177,8 +175,6 @@ inline void convertV210ToRgba(
|
|||||||
default: y10 = y0; cb10 = cb0; cr10 = cr0; break;
|
default: y10 = y0; cb10 = cb0; cr10 = cr0; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BT.709 limited-range YCbCr to RGB. v210 stores 10-bit video-range
|
|
||||||
// samples: Y [64, 940], Cb/Cr centered at 512.
|
|
||||||
int32_t yp = (static_cast<int32_t>(y10) - 64) >> 2;
|
int32_t yp = (static_cast<int32_t>(y10) - 64) >> 2;
|
||||||
int32_t cbp = (static_cast<int32_t>(cb10) - 512) >> 2;
|
int32_t cbp = (static_cast<int32_t>(cb10) - 512) >> 2;
|
||||||
int32_t crp = (static_cast<int32_t>(cr10) - 512) >> 2;
|
int32_t crp = (static_cast<int32_t>(cr10) - 512) >> 2;
|
||||||
@@ -191,4 +187,4 @@ inline void convertV210ToRgba(
|
|||||||
{clampUint8(r), clampUint8(g), clampUint8(b)});
|
{clampUint8(r), clampUint8(g), clampUint8(b)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# MXL Multiviewer
|
|
||||||
|
|
||||||
Small Vulkan/SDL3 multiviewer for test feeds and MXL SDK video flows.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
Install:
|
|
||||||
|
|
||||||
- CMake 3.20+
|
|
||||||
- C++20 compiler
|
|
||||||
- Vulkan loader and headers
|
|
||||||
- Vulkan driver for the target GPU
|
|
||||||
- SDL3 development package with `pkg-config` support
|
|
||||||
- `glslangValidator`
|
|
||||||
|
|
||||||
On Ubuntu/Debian-like systems the packages are usually similar to:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt install cmake g++ pkg-config libvulkan-dev vulkan-tools glslang-tools libsdl3-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Check that the discrete GPU is visible:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vulkaninfo --summary
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build Without MXL SDK
|
|
||||||
|
|
||||||
This builds synthetic feeds, SMPTE bars, and no-signal mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build -j
|
|
||||||
```
|
|
||||||
|
|
||||||
Run a synthetic v210 test grid:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./build/mxl_multiviewer --perf --grid 4x4 v210 v210 v210 v210 v210 v210 v210 v210 v210
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build With MXL SDK
|
|
||||||
|
|
||||||
Set `MXL_SDK_ROOT` to the SDK checkout/root that contains `lib/include` and `build/Linux-Clang-Release/lib`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cmake -S . -B build-sdk \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DMXL_MULTIVIEWER_ENABLE_MXL_SDK=ON \
|
|
||||||
-DMXL_SDK_ROOT=/path/to/mxl-sdk
|
|
||||||
|
|
||||||
cmake --build build-sdk -j
|
|
||||||
```
|
|
||||||
|
|
||||||
Run with an existing feed config:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./build-sdk/mxl_multiviewer --perf --grid 4x4 --config /tmp/feeds_config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Each feed object can include an optional `label` field to override the tile UI text:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"domain": "/tmp/mxl",
|
|
||||||
"flow": "853a9cda-0f7a-4b48-b2df-b3f628440150",
|
|
||||||
"label": "Camera 1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Useful probe flags:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
--v210-upload-worker
|
|
||||||
--adaptive-feed-texture
|
|
||||||
--v210-staging-memory default|cached|device-local
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./build-sdk/mxl_multiviewer \
|
|
||||||
--adaptive-feed-texture \
|
|
||||||
--v210-upload-worker \
|
|
||||||
--perf \
|
|
||||||
--grid 4x4 \
|
|
||||||
--config /tmp/feeds_config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Run from the repository root or keep the `shaders/` directory next to the executable path expected by the app.
|
|
||||||
- If SDL opens on the wrong GPU/session, try running from the desktop session attached to the discrete GPU and confirm with `vulkaninfo --summary`.
|
|
||||||
- `v210_test` is a CPU-side conversion sanity check:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./build/v210_test
|
|
||||||
```
|
|
||||||
+39
-3
@@ -216,6 +216,7 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
uint32_t srcStride,
|
uint32_t srcStride,
|
||||||
uint32_t dstWidth,
|
uint32_t dstWidth,
|
||||||
uint32_t dstHeight,
|
uint32_t dstHeight,
|
||||||
|
V210StagingMemoryMode stagingMemoryMode,
|
||||||
V210ComputeFeed& feed)
|
V210ComputeFeed& feed)
|
||||||
{
|
{
|
||||||
feed.srcWidth = srcWidth;
|
feed.srcWidth = srcWidth;
|
||||||
@@ -224,15 +225,27 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
|
|
||||||
VkDeviceSize v210Size = static_cast<VkDeviceSize>(srcStride) * srcHeight;
|
VkDeviceSize v210Size = static_cast<VkDeviceSize>(srcStride) * srcHeight;
|
||||||
|
|
||||||
createBuffer(
|
VkMemoryPropertyFlags preferredProperties = 0;
|
||||||
|
if (stagingMemoryMode == V210StagingMemoryMode::Cached)
|
||||||
|
{
|
||||||
|
preferredProperties = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
|
||||||
|
}
|
||||||
|
else if (stagingMemoryMode == V210StagingMemoryMode::DeviceLocal)
|
||||||
|
{
|
||||||
|
preferredProperties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
createBufferWithPreferredMemory(
|
||||||
m_device,
|
m_device,
|
||||||
physicalDevice,
|
physicalDevice,
|
||||||
v210Size,
|
v210Size,
|
||||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
||||||
|
preferredProperties,
|
||||||
feed.v210Buffer,
|
feed.v210Buffer,
|
||||||
feed.v210BufferMemory);
|
feed.v210BufferMemory,
|
||||||
|
feed.v210MemoryProperties);
|
||||||
|
|
||||||
if (vkMapMemory(
|
if (vkMapMemory(
|
||||||
m_device,
|
m_device,
|
||||||
@@ -246,6 +259,29 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
"Failed to map v210 buffer memory");
|
"Failed to map v210 buffer memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "Feed " << (feedIndex + 1)
|
||||||
|
<< ": v210 staging memory mode="
|
||||||
|
<< v210StagingMemoryModeName(stagingMemoryMode)
|
||||||
|
<< " flags=0x" << std::hex << feed.v210MemoryProperties
|
||||||
|
<< std::dec << std::endl;
|
||||||
|
|
||||||
|
VkFormatProperties outputFormatProperties{};
|
||||||
|
vkGetPhysicalDeviceFormatProperties(
|
||||||
|
physicalDevice,
|
||||||
|
VK_FORMAT_R8G8B8A8_UNORM,
|
||||||
|
&outputFormatProperties);
|
||||||
|
const VkFormatFeatureFlags requiredOutputFeatures =
|
||||||
|
VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT |
|
||||||
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||||
|
if ((outputFormatProperties.optimalTilingFeatures &
|
||||||
|
requiredOutputFeatures) != requiredOutputFeatures)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(
|
||||||
|
"VK_FORMAT_R8G8B8A8_UNORM does not support sampled "
|
||||||
|
"storage images on this GPU");
|
||||||
|
}
|
||||||
|
|
||||||
createImage(
|
createImage(
|
||||||
m_device,
|
m_device,
|
||||||
physicalDevice,
|
physicalDevice,
|
||||||
@@ -330,7 +366,7 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||||
imageInfo.imageView = feed.imageView;
|
imageInfo.imageView = feed.imageView;
|
||||||
|
|
||||||
VkWriteDescriptorSet writes[2]{};
|
VkWriteDescriptorSet writes[2];
|
||||||
|
|
||||||
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
writes[0].dstSet = feed.descriptorSet;
|
writes[0].dstSet = feed.descriptorSet;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "AppConfig.hpp"
|
||||||
|
|
||||||
#include <vulkan/vulkan.h>
|
#include <vulkan/vulkan.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -10,6 +12,7 @@ struct V210ComputeFeed
|
|||||||
VkBuffer v210Buffer = VK_NULL_HANDLE;
|
VkBuffer v210Buffer = VK_NULL_HANDLE;
|
||||||
VkDeviceMemory v210BufferMemory = VK_NULL_HANDLE;
|
VkDeviceMemory v210BufferMemory = VK_NULL_HANDLE;
|
||||||
void* v210MappedData = nullptr;
|
void* v210MappedData = nullptr;
|
||||||
|
VkMemoryPropertyFlags v210MemoryProperties = 0;
|
||||||
|
|
||||||
VkImage image = VK_NULL_HANDLE;
|
VkImage image = VK_NULL_HANDLE;
|
||||||
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
|
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
|
||||||
@@ -44,6 +47,7 @@ public:
|
|||||||
uint32_t srcStride,
|
uint32_t srcStride,
|
||||||
uint32_t dstWidth,
|
uint32_t dstWidth,
|
||||||
uint32_t dstHeight,
|
uint32_t dstHeight,
|
||||||
|
V210StagingMemoryMode stagingMemoryMode,
|
||||||
V210ComputeFeed& feed);
|
V210ComputeFeed& feed);
|
||||||
|
|
||||||
void destroyFeedResources(V210ComputeFeed& feed);
|
void destroyFeedResources(V210ComputeFeed& feed);
|
||||||
|
|||||||
+4
-2
@@ -30,8 +30,10 @@ QueueFamilyIndices findQueueFamilies(
|
|||||||
|
|
||||||
for (uint32_t i = 0; i < queueFamilyCount; ++i)
|
for (uint32_t i = 0; i < queueFamilyCount; ++i)
|
||||||
{
|
{
|
||||||
if (queueFamilies[i].queueFlags &
|
if ((queueFamilies[i].queueFlags &
|
||||||
VK_QUEUE_GRAPHICS_BIT)
|
VK_QUEUE_GRAPHICS_BIT) &&
|
||||||
|
(queueFamilies[i].queueFlags &
|
||||||
|
VK_QUEUE_COMPUTE_BIT))
|
||||||
{
|
{
|
||||||
indices.graphicsFamily = i;
|
indices.graphicsFamily = i;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-6
@@ -117,7 +117,10 @@ void createBuffer(
|
|||||||
throw std::runtime_error("Failed to allocate buffer memory");
|
throw std::runtime_error("Failed to allocate buffer memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
vkBindBufferMemory(device, buffer, bufferMemory, 0);
|
if (vkBindBufferMemory(device, buffer, bufferMemory, 0) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to bind buffer memory");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void createBufferWithPreferredMemory(
|
void createBufferWithPreferredMemory(
|
||||||
@@ -161,7 +164,10 @@ void createBufferWithPreferredMemory(
|
|||||||
throw std::runtime_error("Failed to allocate buffer memory");
|
throw std::runtime_error("Failed to allocate buffer memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
vkBindBufferMemory(device, buffer, bufferMemory, 0);
|
if (vkBindBufferMemory(device, buffer, bufferMemory, 0) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to bind buffer memory");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VkCommandBuffer beginSingleTimeCommands(
|
VkCommandBuffer beginSingleTimeCommands(
|
||||||
@@ -192,15 +198,29 @@ void endSingleTimeCommands(
|
|||||||
VkQueue graphicsQueue,
|
VkQueue graphicsQueue,
|
||||||
VkCommandBuffer commandBuffer)
|
VkCommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
vkEndCommandBuffer(commandBuffer);
|
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to end one-time command buffer");
|
||||||
|
}
|
||||||
|
|
||||||
VkSubmitInfo submitInfo{};
|
VkSubmitInfo submitInfo{};
|
||||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
submitInfo.commandBufferCount = 1;
|
submitInfo.commandBufferCount = 1;
|
||||||
submitInfo.pCommandBuffers = &commandBuffer;
|
submitInfo.pCommandBuffers = &commandBuffer;
|
||||||
|
|
||||||
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
|
if (vkQueueSubmit(
|
||||||
vkQueueWaitIdle(graphicsQueue);
|
graphicsQueue,
|
||||||
|
1,
|
||||||
|
&submitInfo,
|
||||||
|
VK_NULL_HANDLE) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to submit one-time command buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vkQueueWaitIdle(graphicsQueue) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to wait for one-time command buffer");
|
||||||
|
}
|
||||||
|
|
||||||
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
|
vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
|
||||||
}
|
}
|
||||||
@@ -254,7 +274,10 @@ void createImage(
|
|||||||
throw std::runtime_error("Failed to allocate image memory");
|
throw std::runtime_error("Failed to allocate image memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
vkBindImageMemory(device, image, imageMemory, 0);
|
if (vkBindImageMemory(device, image, imageMemory, 0) != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to bind image memory");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void recordImageLayoutTransition(
|
void recordImageLayoutTransition(
|
||||||
|
|||||||
+2
-4
@@ -7,13 +7,11 @@
|
|||||||
"feeds": [
|
"feeds": [
|
||||||
{
|
{
|
||||||
"domain": "/tmp/mxl",
|
"domain": "/tmp/mxl",
|
||||||
"flow": "853a9cda-0f7a-4b48-b2df-b3f628440150",
|
"flow": "853a9cda-0f7a-4b48-b2df-b3f628440150"
|
||||||
"label": "Camera 1"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"domain": "/tmp/mxl",
|
"domain": "/tmp/mxl",
|
||||||
"flow": "fab60e17-f721-4e12-a7dd-312f5f6e502d",
|
"flow": "fab60e17-f721-4e12-a7dd-312f5f6e502d"
|
||||||
"label": "Camera 2"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"kind": "nosignal"
|
"kind": "nosignal"
|
||||||
|
|||||||
@@ -18,15 +18,20 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <deque>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <cmath>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
@@ -41,6 +46,50 @@ constexpr float TILE_ASPECT = 16.0f / 9.0f;
|
|||||||
|
|
||||||
static std::atomic<bool> g_running{true};
|
static std::atomic<bool> g_running{true};
|
||||||
|
|
||||||
|
struct FeedTextureSize
|
||||||
|
{
|
||||||
|
uint32_t width = TEXTURE_WIDTH;
|
||||||
|
uint32_t height = TEXTURE_HEIGHT;
|
||||||
|
};
|
||||||
|
|
||||||
|
FeedTextureSize chooseFeedTextureSize(
|
||||||
|
const AppConfig& config,
|
||||||
|
int windowWidth,
|
||||||
|
int windowHeight)
|
||||||
|
{
|
||||||
|
if (!config.adaptiveFeedTexture)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<TileRect> rects =
|
||||||
|
makeGridTileRects(
|
||||||
|
config.gridCols,
|
||||||
|
config.gridRows,
|
||||||
|
static_cast<float>(windowWidth),
|
||||||
|
static_cast<float>(windowHeight),
|
||||||
|
TILE_GAP_PIXELS,
|
||||||
|
TILE_ASPECT);
|
||||||
|
|
||||||
|
if (rects.empty())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const TileRect& rect = rects.front();
|
||||||
|
const uint32_t tileWidth =
|
||||||
|
static_cast<uint32_t>(
|
||||||
|
std::max(1.0f, std::ceil(rect.x1 - rect.x0)));
|
||||||
|
const uint32_t tileHeight =
|
||||||
|
static_cast<uint32_t>(
|
||||||
|
std::max(1.0f, std::ceil(rect.y1 - rect.y0)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
std::min(TEXTURE_WIDTH, tileWidth),
|
||||||
|
std::min(TEXTURE_HEIGHT, tileHeight)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
struct PerfStats
|
struct PerfStats
|
||||||
{
|
{
|
||||||
uint64_t frames = 0;
|
uint64_t frames = 0;
|
||||||
@@ -49,6 +98,8 @@ struct PerfStats
|
|||||||
uint64_t stageCopyTicks = 0;
|
uint64_t stageCopyTicks = 0;
|
||||||
uint64_t directV210SourceReadNs = 0;
|
uint64_t directV210SourceReadNs = 0;
|
||||||
uint64_t directV210PayloadCopyNs = 0;
|
uint64_t directV210PayloadCopyNs = 0;
|
||||||
|
uint64_t workerV210SourceReadNs = 0;
|
||||||
|
uint64_t workerV210PayloadCopyNs = 0;
|
||||||
uint64_t uploadRecordTicks = 0;
|
uint64_t uploadRecordTicks = 0;
|
||||||
uint64_t submitPresentTicks = 0;
|
uint64_t submitPresentTicks = 0;
|
||||||
uint64_t idleDelayTicks = 0;
|
uint64_t idleDelayTicks = 0;
|
||||||
@@ -69,6 +120,8 @@ struct FramePerfStats
|
|||||||
uint64_t stageCopyTicks = 0;
|
uint64_t stageCopyTicks = 0;
|
||||||
uint64_t directV210SourceReadNs = 0;
|
uint64_t directV210SourceReadNs = 0;
|
||||||
uint64_t directV210PayloadCopyNs = 0;
|
uint64_t directV210PayloadCopyNs = 0;
|
||||||
|
uint64_t workerV210SourceReadNs = 0;
|
||||||
|
uint64_t workerV210PayloadCopyNs = 0;
|
||||||
uint64_t maxStageCopyTicks = 0;
|
uint64_t maxStageCopyTicks = 0;
|
||||||
uint64_t maxStageCopyBytes = 0;
|
uint64_t maxStageCopyBytes = 0;
|
||||||
uint32_t maxStageCopyFeed = 0;
|
uint32_t maxStageCopyFeed = 0;
|
||||||
@@ -95,6 +148,129 @@ struct FramePerfStats
|
|||||||
uint32_t maxProcessEventType = 0;
|
uint32_t maxProcessEventType = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct V210UploadJob
|
||||||
|
{
|
||||||
|
uint32_t feedIndex = 0;
|
||||||
|
uint32_t frameCounter = 0;
|
||||||
|
IVideoFeed* feed = nullptr;
|
||||||
|
void* destination = nullptr;
|
||||||
|
size_t destinationSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct V210UploadResult
|
||||||
|
{
|
||||||
|
uint32_t feedIndex = 0;
|
||||||
|
bool copied = false;
|
||||||
|
uint64_t version = 0;
|
||||||
|
V210ReadTiming timing{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class V210UploadWorker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
V210UploadWorker()
|
||||||
|
: mThread(&V210UploadWorker::run, this)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~V210UploadWorker()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
V210UploadWorker(const V210UploadWorker&) = delete;
|
||||||
|
V210UploadWorker& operator=(const V210UploadWorker&) = delete;
|
||||||
|
|
||||||
|
void enqueue(V210UploadJob job)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
mJobs.push_back(job);
|
||||||
|
}
|
||||||
|
mCondition.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void drainResults(std::vector<V210UploadResult>& results)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
while (!mResults.empty())
|
||||||
|
{
|
||||||
|
results.push_back(mResults.front());
|
||||||
|
mResults.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
if (mStopping)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mStopping = true;
|
||||||
|
}
|
||||||
|
mCondition.notify_one();
|
||||||
|
if (mThread.joinable())
|
||||||
|
{
|
||||||
|
mThread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
V210UploadJob job{};
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
|
mCondition.wait(
|
||||||
|
lock,
|
||||||
|
[&]
|
||||||
|
{
|
||||||
|
return mStopping || !mJobs.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mStopping && mJobs.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
job = mJobs.front();
|
||||||
|
mJobs.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
V210UploadResult result{};
|
||||||
|
result.feedIndex = job.feedIndex;
|
||||||
|
if (job.feed != nullptr && job.destination != nullptr)
|
||||||
|
{
|
||||||
|
result.copied = job.feed->readV210FrameInto(
|
||||||
|
job.frameCounter,
|
||||||
|
job.destination,
|
||||||
|
job.destinationSize,
|
||||||
|
&result.timing);
|
||||||
|
if (result.copied)
|
||||||
|
{
|
||||||
|
result.version = job.feed->frameVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
|
mResults.push_back(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mutex mMutex;
|
||||||
|
std::condition_variable mCondition;
|
||||||
|
std::deque<V210UploadJob> mJobs;
|
||||||
|
std::deque<V210UploadResult> mResults;
|
||||||
|
bool mStopping = false;
|
||||||
|
std::thread mThread;
|
||||||
|
};
|
||||||
|
|
||||||
struct FeedPerfStats
|
struct FeedPerfStats
|
||||||
{
|
{
|
||||||
uint64_t updates = 0;
|
uint64_t updates = 0;
|
||||||
@@ -464,7 +640,6 @@ static ImU32 tileLabelColor(
|
|||||||
|
|
||||||
static void drawTileLabels(
|
static void drawTileLabels(
|
||||||
const std::vector<std::unique_ptr<IVideoFeed>>& feeds,
|
const std::vector<std::unique_ptr<IVideoFeed>>& feeds,
|
||||||
const std::vector<FeedConfig>& feedConfigs,
|
|
||||||
uint32_t gridCols,
|
uint32_t gridCols,
|
||||||
uint32_t gridRows)
|
uint32_t gridRows)
|
||||||
{
|
{
|
||||||
@@ -493,20 +668,12 @@ static void drawTileLabels(
|
|||||||
feeds[i]->status();
|
feeds[i]->status();
|
||||||
const std::string sourceInfo =
|
const std::string sourceInfo =
|
||||||
feeds[i]->sourceInfo();
|
feeds[i]->sourceInfo();
|
||||||
const std::string labelOverride =
|
|
||||||
i < feedConfigs.size()
|
|
||||||
? feedConfigs[i].label
|
|
||||||
: "";
|
|
||||||
const std::string labelText =
|
|
||||||
!labelOverride.empty()
|
|
||||||
? labelOverride
|
|
||||||
: sourceInfo.empty()
|
|
||||||
? feedRuntimeStatusDisplayName(status)
|
|
||||||
: sourceInfo;
|
|
||||||
const std::string label =
|
const std::string label =
|
||||||
std::to_string(i + 1) +
|
std::to_string(i + 1) +
|
||||||
": " +
|
": " +
|
||||||
labelText;
|
(sourceInfo.empty()
|
||||||
|
? feedRuntimeStatusDisplayName(status)
|
||||||
|
: sourceInfo);
|
||||||
|
|
||||||
const ImVec2 labelMin(
|
const ImVec2 labelMin(
|
||||||
tileRect.x0,
|
tileRect.x0,
|
||||||
@@ -523,21 +690,10 @@ static void drawTileLabels(
|
|||||||
labelMax,
|
labelMax,
|
||||||
IM_COL32(0, 0, 0, 175)
|
IM_COL32(0, 0, 0, 175)
|
||||||
);
|
);
|
||||||
const ImVec4 labelClip(
|
|
||||||
labelMin.x,
|
|
||||||
labelMin.y,
|
|
||||||
labelMax.x,
|
|
||||||
labelMax.y
|
|
||||||
);
|
|
||||||
drawList->AddText(
|
drawList->AddText(
|
||||||
nullptr,
|
|
||||||
0.0f,
|
|
||||||
textPos,
|
textPos,
|
||||||
tileLabelColor(status),
|
tileLabelColor(status),
|
||||||
label.c_str(),
|
label.c_str()
|
||||||
nullptr,
|
|
||||||
0.0f,
|
|
||||||
&labelClip
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,6 +794,19 @@ int main(int argc, char* argv[])
|
|||||||
int winWidth, winHeight;
|
int winWidth, winHeight;
|
||||||
SDL_GetWindowSize(window, &winWidth, &winHeight);
|
SDL_GetWindowSize(window, &winWidth, &winHeight);
|
||||||
|
|
||||||
|
const FeedTextureSize feedTextureSize =
|
||||||
|
chooseFeedTextureSize(config, winWidth, winHeight);
|
||||||
|
const uint32_t feedTextureWidth = feedTextureSize.width;
|
||||||
|
const uint32_t feedTextureHeight = feedTextureSize.height;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "Feed texture size: "
|
||||||
|
<< feedTextureWidth
|
||||||
|
<< "x"
|
||||||
|
<< feedTextureHeight
|
||||||
|
<< (config.adaptiveFeedTexture ? " adaptive" : " fixed")
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
Swapchain swapchain(
|
Swapchain swapchain(
|
||||||
ctx.device(),
|
ctx.device(),
|
||||||
ctx.physicalDevice(),
|
ctx.physicalDevice(),
|
||||||
@@ -725,19 +894,13 @@ int main(int argc, char* argv[])
|
|||||||
<< " flowId=" << config.feeds[i].mxlFlowId;
|
<< " flowId=" << config.feeds[i].mxlFlowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.feeds[i].label.empty())
|
|
||||||
{
|
|
||||||
std::cout
|
|
||||||
<< " label=\"" << config.feeds[i].label << "\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
feeds[i] = createFeed(
|
feeds[i] = createFeed(
|
||||||
config.feeds[i],
|
config.feeds[i],
|
||||||
i,
|
i,
|
||||||
TEXTURE_WIDTH,
|
feedTextureWidth,
|
||||||
TEXTURE_HEIGHT,
|
feedTextureHeight,
|
||||||
config.verbose
|
config.verbose
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -747,7 +910,8 @@ int main(int argc, char* argv[])
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
VkDeviceSize imageSize =
|
VkDeviceSize imageSize =
|
||||||
TEXTURE_WIDTH * TEXTURE_HEIGHT * 4;
|
static_cast<VkDeviceSize>(feedTextureWidth) *
|
||||||
|
feedTextureHeight * 4;
|
||||||
|
|
||||||
std::vector<FeedTexture> feedTextures(feedCount);
|
std::vector<FeedTexture> feedTextures(feedCount);
|
||||||
|
|
||||||
@@ -855,8 +1019,8 @@ int main(int argc, char* argv[])
|
|||||||
v210Decoder.init(
|
v210Decoder.init(
|
||||||
ctx.device(),
|
ctx.device(),
|
||||||
feedCount,
|
feedCount,
|
||||||
TEXTURE_WIDTH,
|
feedTextureWidth,
|
||||||
TEXTURE_HEIGHT
|
feedTextureHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
std::vector<V210ComputeFeed> v210Feeds(feedCount);
|
std::vector<V210ComputeFeed> v210Feeds(feedCount);
|
||||||
@@ -868,8 +1032,16 @@ int main(int argc, char* argv[])
|
|||||||
std::vector<bool> pendingUploadNeeded(feedCount);
|
std::vector<bool> pendingUploadNeeded(feedCount);
|
||||||
std::vector<bool> postFenceDirectV210Read(feedCount);
|
std::vector<bool> postFenceDirectV210Read(feedCount);
|
||||||
std::vector<double> feedUploadCredits(feedCount, 1.0);
|
std::vector<double> feedUploadCredits(feedCount, 1.0);
|
||||||
|
std::vector<bool> v210WorkerInFlight(feedCount);
|
||||||
|
std::unique_ptr<V210UploadWorker> v210UploadWorker;
|
||||||
|
std::vector<V210UploadResult> v210WorkerResults;
|
||||||
uint64_t lastUploadPaceTicks = SDL_GetPerformanceCounter();
|
uint64_t lastUploadPaceTicks = SDL_GetPerformanceCounter();
|
||||||
|
|
||||||
|
if (config.v210UploadWorker)
|
||||||
|
{
|
||||||
|
v210UploadWorker = std::make_unique<V210UploadWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
std::fill(
|
std::fill(
|
||||||
uploadedVersions.begin(),
|
uploadedVersions.begin(),
|
||||||
uploadedVersions.end(),
|
uploadedVersions.end(),
|
||||||
@@ -1458,6 +1630,40 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto recordFeedUploadStats =
|
||||||
|
[&](uint32_t feedIndex)
|
||||||
|
{
|
||||||
|
if (!config.logPerf)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++feedPerfStats[feedIndex].uploads;
|
||||||
|
|
||||||
|
const uint64_t uploadTicks =
|
||||||
|
SDL_GetPerformanceCounter();
|
||||||
|
if (feedPerfStats[feedIndex].lastUploadTicks != 0)
|
||||||
|
{
|
||||||
|
const uint64_t interval =
|
||||||
|
uploadTicks -
|
||||||
|
feedPerfStats[feedIndex].lastUploadTicks;
|
||||||
|
feedPerfStats[feedIndex].uploadIntervalTicks +=
|
||||||
|
interval;
|
||||||
|
feedPerfStats[feedIndex].minUploadIntervalTicks =
|
||||||
|
std::min(
|
||||||
|
feedPerfStats[feedIndex].minUploadIntervalTicks,
|
||||||
|
interval
|
||||||
|
);
|
||||||
|
feedPerfStats[feedIndex].maxUploadIntervalTicks =
|
||||||
|
std::max(
|
||||||
|
feedPerfStats[feedIndex].maxUploadIntervalTicks,
|
||||||
|
interval
|
||||||
|
);
|
||||||
|
++feedPerfStats[feedIndex].uploadIntervals;
|
||||||
|
}
|
||||||
|
feedPerfStats[feedIndex].lastUploadTicks = uploadTicks;
|
||||||
|
};
|
||||||
|
|
||||||
for (uint32_t i = 0; i < feedCount; ++i)
|
for (uint32_t i = 0; i < feedCount; ++i)
|
||||||
{
|
{
|
||||||
if (v210FeedReady[i] &&
|
if (v210FeedReady[i] &&
|
||||||
@@ -1526,6 +1732,54 @@ int main(int argc, char* argv[])
|
|||||||
framePerf.submitPresentTicks += elapsed;
|
framePerf.submitPresentTicks += elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.v210UploadWorker)
|
||||||
|
{
|
||||||
|
v210WorkerResults.clear();
|
||||||
|
v210UploadWorker->drainResults(v210WorkerResults);
|
||||||
|
|
||||||
|
for (const V210UploadResult& result : v210WorkerResults)
|
||||||
|
{
|
||||||
|
const uint32_t i = result.feedIndex;
|
||||||
|
if (i >= feedCount)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
v210WorkerInFlight[i] = false;
|
||||||
|
|
||||||
|
if (!result.copied)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool uploadNeeded =
|
||||||
|
uploadedVersions[i] != result.version;
|
||||||
|
recordFeedVersionStats(i, uploadNeeded);
|
||||||
|
|
||||||
|
if (!uploadNeeded)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingVersions[i] = result.version;
|
||||||
|
uploadedVersions[i] = result.version;
|
||||||
|
feedUploadNeeded[i] = true;
|
||||||
|
recordFeedUploadStats(i);
|
||||||
|
|
||||||
|
if (config.logPerf)
|
||||||
|
{
|
||||||
|
perfStats.workerV210SourceReadNs +=
|
||||||
|
result.timing.sourceReadNs;
|
||||||
|
perfStats.workerV210PayloadCopyNs +=
|
||||||
|
result.timing.payloadCopyNs;
|
||||||
|
framePerf.workerV210SourceReadNs +=
|
||||||
|
result.timing.sourceReadNs;
|
||||||
|
framePerf.workerV210PayloadCopyNs +=
|
||||||
|
result.timing.payloadCopyNs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
++frameCounter;
|
++frameCounter;
|
||||||
|
|
||||||
if (config.logFps)
|
if (config.logFps)
|
||||||
@@ -1611,8 +1865,9 @@ int main(int argc, char* argv[])
|
|||||||
feeds[i]->v210Width(),
|
feeds[i]->v210Width(),
|
||||||
feeds[i]->v210Height(),
|
feeds[i]->v210Height(),
|
||||||
feeds[i]->v210Stride(),
|
feeds[i]->v210Stride(),
|
||||||
TEXTURE_WIDTH,
|
feedTextureWidth,
|
||||||
TEXTURE_HEIGHT,
|
feedTextureHeight,
|
||||||
|
config.v210StagingMemoryMode,
|
||||||
v210Feeds[i]
|
v210Feeds[i]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1640,6 +1895,42 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
if (directV210Read)
|
if (directV210Read)
|
||||||
{
|
{
|
||||||
|
if (config.v210UploadWorker)
|
||||||
|
{
|
||||||
|
if (!v210WorkerInFlight[i])
|
||||||
|
{
|
||||||
|
const size_t v210Bytes =
|
||||||
|
static_cast<size_t>(
|
||||||
|
feeds[i]->v210Stride()
|
||||||
|
) *
|
||||||
|
static_cast<size_t>(
|
||||||
|
feeds[i]->v210Height()
|
||||||
|
);
|
||||||
|
|
||||||
|
v210UploadWorker->enqueue(
|
||||||
|
V210UploadJob{
|
||||||
|
i,
|
||||||
|
frameCounter,
|
||||||
|
feeds[i].get(),
|
||||||
|
v210Feeds[i].v210MappedData,
|
||||||
|
v210Bytes
|
||||||
|
});
|
||||||
|
v210WorkerInFlight[i] = true;
|
||||||
|
++v210UploadsThisFrame;
|
||||||
|
|
||||||
|
if (config.paceUploads &&
|
||||||
|
feeds[i]->hasFrameRate())
|
||||||
|
{
|
||||||
|
feedUploadCredits[i] =
|
||||||
|
std::max(
|
||||||
|
0.0,
|
||||||
|
feedUploadCredits[i] - 1.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
V210ReadTiming directReadTiming{};
|
V210ReadTiming directReadTiming{};
|
||||||
const size_t v210Bytes =
|
const size_t v210Bytes =
|
||||||
static_cast<size_t>(
|
static_cast<size_t>(
|
||||||
@@ -1745,30 +2036,7 @@ int main(int argc, char* argv[])
|
|||||||
}
|
}
|
||||||
if (config.logPerf)
|
if (config.logPerf)
|
||||||
{
|
{
|
||||||
++feedPerfStats[i].uploads;
|
recordFeedUploadStats(i);
|
||||||
|
|
||||||
const uint64_t uploadTicks =
|
|
||||||
SDL_GetPerformanceCounter();
|
|
||||||
if (feedPerfStats[i].lastUploadTicks != 0)
|
|
||||||
{
|
|
||||||
const uint64_t interval =
|
|
||||||
uploadTicks -
|
|
||||||
feedPerfStats[i].lastUploadTicks;
|
|
||||||
feedPerfStats[i].uploadIntervalTicks +=
|
|
||||||
interval;
|
|
||||||
feedPerfStats[i].minUploadIntervalTicks =
|
|
||||||
std::min(
|
|
||||||
feedPerfStats[i].minUploadIntervalTicks,
|
|
||||||
interval
|
|
||||||
);
|
|
||||||
feedPerfStats[i].maxUploadIntervalTicks =
|
|
||||||
std::max(
|
|
||||||
feedPerfStats[i].maxUploadIntervalTicks,
|
|
||||||
interval
|
|
||||||
);
|
|
||||||
++feedPerfStats[i].uploadIntervals;
|
|
||||||
}
|
|
||||||
feedPerfStats[i].lastUploadTicks = uploadTicks;
|
|
||||||
}
|
}
|
||||||
feedUploadNeeded[i] = true;
|
feedUploadNeeded[i] = true;
|
||||||
}
|
}
|
||||||
@@ -1824,7 +2092,6 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
drawTileLabels(
|
drawTileLabels(
|
||||||
feeds,
|
feeds,
|
||||||
config.feeds,
|
|
||||||
config.gridCols,
|
config.gridCols,
|
||||||
config.gridRows
|
config.gridRows
|
||||||
);
|
);
|
||||||
@@ -1876,8 +2143,8 @@ int main(int argc, char* argv[])
|
|||||||
feeds[i]->v210Width(),
|
feeds[i]->v210Width(),
|
||||||
feeds[i]->v210Height(),
|
feeds[i]->v210Height(),
|
||||||
feeds[i]->v210Stride(),
|
feeds[i]->v210Stride(),
|
||||||
TEXTURE_WIDTH,
|
feedTextureWidth,
|
||||||
TEXTURE_HEIGHT
|
feedTextureHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
v210Decoder.recordOutputReadyForSampling(
|
v210Decoder.recordOutputReadyForSampling(
|
||||||
@@ -1890,8 +2157,8 @@ int main(int argc, char* argv[])
|
|||||||
recordFeedTextureUpload(
|
recordFeedTextureUpload(
|
||||||
uploadCmdBuf,
|
uploadCmdBuf,
|
||||||
feedTextures[i],
|
feedTextures[i],
|
||||||
TEXTURE_WIDTH,
|
feedTextureWidth,
|
||||||
TEXTURE_HEIGHT
|
feedTextureHeight
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2170,6 +2437,14 @@ int main(int argc, char* argv[])
|
|||||||
<< (static_cast<double>(
|
<< (static_cast<double>(
|
||||||
framePerf.directV210PayloadCopyNs) /
|
framePerf.directV210PayloadCopyNs) /
|
||||||
1000000.0)
|
1000000.0)
|
||||||
|
<< "ms v210WorkerRead="
|
||||||
|
<< (static_cast<double>(
|
||||||
|
framePerf.workerV210SourceReadNs) /
|
||||||
|
1000000.0)
|
||||||
|
<< "ms v210WorkerCopy="
|
||||||
|
<< (static_cast<double>(
|
||||||
|
framePerf.workerV210PayloadCopyNs) /
|
||||||
|
1000000.0)
|
||||||
<< "ms stageCopies="
|
<< "ms stageCopies="
|
||||||
<< framePerf.stageCopies
|
<< framePerf.stageCopies
|
||||||
<< " v210Copies="
|
<< " v210Copies="
|
||||||
@@ -2229,6 +2504,14 @@ int main(int argc, char* argv[])
|
|||||||
<< (static_cast<double>(
|
<< (static_cast<double>(
|
||||||
perfStats.directV210PayloadCopyNs) /
|
perfStats.directV210PayloadCopyNs) /
|
||||||
1000000.0 / frames)
|
1000000.0 / frames)
|
||||||
|
<< "ms v210WorkerRead="
|
||||||
|
<< (static_cast<double>(
|
||||||
|
perfStats.workerV210SourceReadNs) /
|
||||||
|
1000000.0 / frames)
|
||||||
|
<< "ms v210WorkerCopy="
|
||||||
|
<< (static_cast<double>(
|
||||||
|
perfStats.workerV210PayloadCopyNs) /
|
||||||
|
1000000.0 / frames)
|
||||||
<< "ms uploadRecord="
|
<< "ms uploadRecord="
|
||||||
<< (perfStats.uploadRecordTicks * invMs / frames)
|
<< (perfStats.uploadRecordTicks * invMs / frames)
|
||||||
<< "ms submitPresent="
|
<< "ms submitPresent="
|
||||||
@@ -2327,6 +2610,11 @@ int main(int argc, char* argv[])
|
|||||||
// Cleanup
|
// Cleanup
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
|
if (v210UploadWorker)
|
||||||
|
{
|
||||||
|
v210UploadWorker->stop();
|
||||||
|
}
|
||||||
|
|
||||||
vkDeviceWaitIdle(ctx.device());
|
vkDeviceWaitIdle(ctx.device());
|
||||||
|
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ void main()
|
|||||||
default: y10 = y5; cb10 = cb4; cr10 = cr4; break;
|
default: y10 = y5; cb10 = cb4; cr10 = cr4; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BT.709 limited-range YCbCr to RGB. v210 stores 10-bit video-range
|
|
||||||
// samples: Y [64, 940], Cb/Cr centered at 512.
|
|
||||||
int yp = int(y10) - 64;
|
int yp = int(y10) - 64;
|
||||||
int cbp = int(cb10) - 512;
|
int cbp = int(cb10) - 512;
|
||||||
int crp = int(cr10) - 512;
|
int crp = int(cr10) - 512;
|
||||||
@@ -81,4 +79,4 @@ void main()
|
|||||||
b = clamp(b, 0, 255);
|
b = clamp(b, 0, 255);
|
||||||
|
|
||||||
imageStore(dstImage, dstPos, vec4(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0, 1.0));
|
imageStore(dstImage, dstPos, vec4(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0, 1.0));
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user