Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ff5f2f90b | |||
| 102b1fb95f | |||
| 58f9f7edaf | |||
| f79eeadde6 | |||
| 22335bb378 | |||
| f99c6cd2c6 |
@@ -660,6 +660,12 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ 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
|
||||||
|
|||||||
+3
-3
@@ -160,10 +160,10 @@ namespace
|
|||||||
{
|
{
|
||||||
const char* keys[] =
|
const char* keys[] =
|
||||||
{
|
{
|
||||||
"name",
|
"label",
|
||||||
"flow_name",
|
|
||||||
"description",
|
"description",
|
||||||
"label"
|
"flow_name",
|
||||||
|
"name"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const char* key : keys)
|
for (const char* key : keys)
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ 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;
|
||||||
@@ -175,6 +177,8 @@ 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;
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
+22
-4
@@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
constexpr VkFormat V210_OUTPUT_FORMAT =
|
||||||
|
VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||||
|
|
||||||
std::vector<char> readFile(const std::string& path)
|
std::vector<char> readFile(const std::string& path)
|
||||||
{
|
{
|
||||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||||
@@ -222,6 +225,21 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
feed.srcHeight = srcHeight;
|
feed.srcHeight = srcHeight;
|
||||||
feed.srcStride = srcStride;
|
feed.srcStride = srcStride;
|
||||||
|
|
||||||
|
VkFormatProperties formatProperties{};
|
||||||
|
vkGetPhysicalDeviceFormatProperties(
|
||||||
|
physicalDevice,
|
||||||
|
V210_OUTPUT_FORMAT,
|
||||||
|
&formatProperties);
|
||||||
|
const VkFormatFeatureFlags requiredFeatures =
|
||||||
|
VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT |
|
||||||
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||||
|
if ((formatProperties.optimalTilingFeatures & requiredFeatures) !=
|
||||||
|
requiredFeatures)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(
|
||||||
|
"VK_FORMAT_R16G16B16A16_SFLOAT cannot be used for v210 output");
|
||||||
|
}
|
||||||
|
|
||||||
VkDeviceSize v210Size = static_cast<VkDeviceSize>(srcStride) * srcHeight;
|
VkDeviceSize v210Size = static_cast<VkDeviceSize>(srcStride) * srcHeight;
|
||||||
|
|
||||||
createBuffer(
|
createBuffer(
|
||||||
@@ -251,7 +269,7 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
physicalDevice,
|
physicalDevice,
|
||||||
dstWidth,
|
dstWidth,
|
||||||
dstHeight,
|
dstHeight,
|
||||||
VK_FORMAT_R8G8B8A8_UNORM,
|
V210_OUTPUT_FORMAT,
|
||||||
VK_IMAGE_TILING_OPTIMAL,
|
VK_IMAGE_TILING_OPTIMAL,
|
||||||
VK_IMAGE_USAGE_STORAGE_BIT |
|
VK_IMAGE_USAGE_STORAGE_BIT |
|
||||||
VK_IMAGE_USAGE_SAMPLED_BIT,
|
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||||
@@ -264,7 +282,7 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
commandPool,
|
commandPool,
|
||||||
graphicsQueue,
|
graphicsQueue,
|
||||||
feed.image,
|
feed.image,
|
||||||
VK_FORMAT_R8G8B8A8_UNORM,
|
V210_OUTPUT_FORMAT,
|
||||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
VK_IMAGE_LAYOUT_GENERAL);
|
VK_IMAGE_LAYOUT_GENERAL);
|
||||||
|
|
||||||
@@ -272,7 +290,7 @@ void V210ComputeDecoder::createFeedResources(
|
|||||||
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
viewInfo.image = feed.image;
|
viewInfo.image = feed.image;
|
||||||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
|
viewInfo.format = V210_OUTPUT_FORMAT;
|
||||||
viewInfo.subresourceRange.aspectMask =
|
viewInfo.subresourceRange.aspectMask =
|
||||||
VK_IMAGE_ASPECT_COLOR_BIT;
|
VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
viewInfo.subresourceRange.baseMipLevel = 0;
|
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||||
@@ -330,7 +348,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;
|
||||||
|
|||||||
+4
-2
@@ -7,11 +7,13 @@
|
|||||||
"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"
|
||||||
|
|||||||
@@ -464,6 +464,7 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -492,12 +493,20 @@ 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) +
|
||||||
": " +
|
": " +
|
||||||
(sourceInfo.empty()
|
labelText;
|
||||||
? feedRuntimeStatusDisplayName(status)
|
|
||||||
: sourceInfo);
|
|
||||||
|
|
||||||
const ImVec2 labelMin(
|
const ImVec2 labelMin(
|
||||||
tileRect.x0,
|
tileRect.x0,
|
||||||
@@ -514,10 +523,21 @@ 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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,6 +725,12 @@ 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(
|
||||||
@@ -1798,6 +1824,7 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
drawTileLabels(
|
drawTileLabels(
|
||||||
feeds,
|
feeds,
|
||||||
|
config.feeds,
|
||||||
config.gridCols,
|
config.gridCols,
|
||||||
config.gridRows
|
config.gridRows
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ layout(binding = 0) readonly buffer V210Data {
|
|||||||
uint data[];
|
uint data[];
|
||||||
} v210;
|
} v210;
|
||||||
|
|
||||||
layout(binding = 1, rgba8) uniform writeonly image2D dstImage;
|
layout(binding = 1, rgba16f) uniform writeonly image2D dstImage;
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
uint srcWidth;
|
uint srcWidth;
|
||||||
@@ -62,6 +62,8 @@ 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user