4 Commits

Author SHA1 Message Date
Johanness d7d0785c66 Harden Vulkan setup for discrete GPUs 2026-05-24 00:15:55 +03:00
Johanness 8ef858e3c8 Add adaptive feed texture probe 2026-05-21 11:35:46 +03:00
Johanness bccf6cfb10 Add v210 upload worker probe 2026-05-19 20:53:51 +03:00
Johanness e4724395f0 Add v210 staging memory probe 2026-05-19 20:41:02 +03:00
7 changed files with 546 additions and 45 deletions
+108
View File
@@ -15,6 +15,9 @@
std::optional<PresentModeConfig> parsePresentMode(
std::string_view text);
std::optional<V210StagingMemoryMode> parseV210StagingMemoryMode(
std::string_view text);
namespace
{
struct JsonValue
@@ -574,6 +577,14 @@ bool applyConfigFile(
{
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 =
jsonString(objectField(*root, "present")))
@@ -588,6 +599,20 @@ bool applyConfigFile(
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 =
jsonArray(objectField(*root, "feeds"));
if (feeds == nullptr)
@@ -721,6 +746,27 @@ std::optional<PresentModeConfig> parsePresentMode(std::string_view text)
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)
{
switch (kind)
@@ -743,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)
{
std::cout
@@ -754,10 +817,13 @@ void printUsage(const char* executableName)
<< " --perf Log per-frame CPU timing breakdown\n"
<< " --verbose Enable detailed startup and SDK logs\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"
<< " --grid <cols>x<rows> Set multiview grid (default 2x2, max 16 feeds)\n"
<< " --fps-cap <fps> Limit render loop FPS (default 60)\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"
<< " --feed <idx> <kind> Set feed slot kind explicitly\n"
<< " --domain <idx> <path> Set MXL domain for feed slot\n"
@@ -899,6 +965,48 @@ ConfigParseResult parseAppConfig(
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")
{
++i;
+13
View File
@@ -30,6 +30,13 @@ enum class PresentModeConfig
Immediate
};
enum class V210StagingMemoryMode
{
Default,
Cached,
DeviceLocal
};
struct FeedConfig
{
FeedKind kind = FeedKind::NoSignal;
@@ -43,16 +50,22 @@ struct AppConfig
bool logPerf = false;
bool verbose = false;
bool paceUploads = false;
bool v210UploadWorker = false;
bool adaptiveFeedTexture = false;
uint32_t fpsCap = 60;
uint32_t maxV210UploadsPerFrame = 0;
uint32_t gridCols = DefaultGridCols;
uint32_t gridRows = DefaultGridRows;
PresentModeConfig presentMode = PresentModeConfig::Mailbox;
V210StagingMemoryMode v210StagingMemoryMode =
V210StagingMemoryMode::Default;
std::vector<FeedConfig> feeds;
};
std::optional<FeedKind> parseFeedKind(std::string_view text);
std::string_view feedKindName(FeedKind kind);
std::string_view v210StagingMemoryModeName(
V210StagingMemoryMode mode);
void printUsage(const char* executableName);
+38 -2
View File
@@ -216,6 +216,7 @@ void V210ComputeDecoder::createFeedResources(
uint32_t srcStride,
uint32_t dstWidth,
uint32_t dstHeight,
V210StagingMemoryMode stagingMemoryMode,
V210ComputeFeed& feed)
{
feed.srcWidth = srcWidth;
@@ -224,15 +225,27 @@ void V210ComputeDecoder::createFeedResources(
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,
physicalDevice,
v210Size,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
preferredProperties,
feed.v210Buffer,
feed.v210BufferMemory);
feed.v210BufferMemory,
feed.v210MemoryProperties);
if (vkMapMemory(
m_device,
@@ -246,6 +259,29 @@ void V210ComputeDecoder::createFeedResources(
"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(
m_device,
physicalDevice,
+4
View File
@@ -1,5 +1,7 @@
#pragma once
#include "AppConfig.hpp"
#include <vulkan/vulkan.h>
#include <cstdint>
@@ -10,6 +12,7 @@ struct V210ComputeFeed
VkBuffer v210Buffer = VK_NULL_HANDLE;
VkDeviceMemory v210BufferMemory = VK_NULL_HANDLE;
void* v210MappedData = nullptr;
VkMemoryPropertyFlags v210MemoryProperties = 0;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory imageMemory = VK_NULL_HANDLE;
@@ -44,6 +47,7 @@ public:
uint32_t srcStride,
uint32_t dstWidth,
uint32_t dstHeight,
V210StagingMemoryMode stagingMemoryMode,
V210ComputeFeed& feed);
void destroyFeedResources(V210ComputeFeed& feed);
+4 -2
View File
@@ -30,8 +30,10 @@ QueueFamilyIndices findQueueFamilies(
for (uint32_t i = 0; i < queueFamilyCount; ++i)
{
if (queueFamilies[i].queueFlags &
VK_QUEUE_GRAPHICS_BIT)
if ((queueFamilies[i].queueFlags &
VK_QUEUE_GRAPHICS_BIT) &&
(queueFamilies[i].queueFlags &
VK_QUEUE_COMPUTE_BIT))
{
indices.graphicsFamily = i;
}
+29 -6
View File
@@ -117,7 +117,10 @@ void createBuffer(
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(
@@ -161,7 +164,10 @@ void createBufferWithPreferredMemory(
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(
@@ -192,15 +198,29 @@ void endSingleTimeCommands(
VkQueue graphicsQueue,
VkCommandBuffer commandBuffer)
{
vkEndCommandBuffer(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS)
{
throw std::runtime_error("Failed to end one-time command buffer");
}
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue);
if (vkQueueSubmit(
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);
}
@@ -254,7 +274,10 @@ void createImage(
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(
+350 -35
View File
@@ -18,15 +18,20 @@
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <cstring>
#include <deque>
#include <csignal>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <limits>
#include <iostream>
#include <map>
#include <memory>
#include <mutex>
#include <sstream>
#include <thread>
#include <vector>
#if defined(__linux__)
@@ -41,6 +46,50 @@ constexpr float TILE_ASPECT = 16.0f / 9.0f;
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
{
uint64_t frames = 0;
@@ -49,6 +98,8 @@ struct PerfStats
uint64_t stageCopyTicks = 0;
uint64_t directV210SourceReadNs = 0;
uint64_t directV210PayloadCopyNs = 0;
uint64_t workerV210SourceReadNs = 0;
uint64_t workerV210PayloadCopyNs = 0;
uint64_t uploadRecordTicks = 0;
uint64_t submitPresentTicks = 0;
uint64_t idleDelayTicks = 0;
@@ -69,6 +120,8 @@ struct FramePerfStats
uint64_t stageCopyTicks = 0;
uint64_t directV210SourceReadNs = 0;
uint64_t directV210PayloadCopyNs = 0;
uint64_t workerV210SourceReadNs = 0;
uint64_t workerV210PayloadCopyNs = 0;
uint64_t maxStageCopyTicks = 0;
uint64_t maxStageCopyBytes = 0;
uint32_t maxStageCopyFeed = 0;
@@ -95,6 +148,129 @@ struct FramePerfStats
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
{
uint64_t updates = 0;
@@ -618,6 +794,19 @@ int main(int argc, char* argv[])
int 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(
ctx.device(),
ctx.physicalDevice(),
@@ -710,8 +899,8 @@ int main(int argc, char* argv[])
feeds[i] = createFeed(
config.feeds[i],
i,
TEXTURE_WIDTH,
TEXTURE_HEIGHT,
feedTextureWidth,
feedTextureHeight,
config.verbose
);
}
@@ -721,7 +910,8 @@ int main(int argc, char* argv[])
// ----------------------------------------
VkDeviceSize imageSize =
TEXTURE_WIDTH * TEXTURE_HEIGHT * 4;
static_cast<VkDeviceSize>(feedTextureWidth) *
feedTextureHeight * 4;
std::vector<FeedTexture> feedTextures(feedCount);
@@ -829,8 +1019,8 @@ int main(int argc, char* argv[])
v210Decoder.init(
ctx.device(),
feedCount,
TEXTURE_WIDTH,
TEXTURE_HEIGHT
feedTextureWidth,
feedTextureHeight
);
std::vector<V210ComputeFeed> v210Feeds(feedCount);
@@ -842,8 +1032,16 @@ int main(int argc, char* argv[])
std::vector<bool> pendingUploadNeeded(feedCount);
std::vector<bool> postFenceDirectV210Read(feedCount);
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();
if (config.v210UploadWorker)
{
v210UploadWorker = std::make_unique<V210UploadWorker>();
}
std::fill(
uploadedVersions.begin(),
uploadedVersions.end(),
@@ -1432,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)
{
if (v210FeedReady[i] &&
@@ -1500,6 +1732,54 @@ int main(int argc, char* argv[])
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;
if (config.logFps)
@@ -1585,8 +1865,9 @@ int main(int argc, char* argv[])
feeds[i]->v210Width(),
feeds[i]->v210Height(),
feeds[i]->v210Stride(),
TEXTURE_WIDTH,
TEXTURE_HEIGHT,
feedTextureWidth,
feedTextureHeight,
config.v210StagingMemoryMode,
v210Feeds[i]
);
@@ -1614,6 +1895,42 @@ int main(int argc, char* argv[])
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{};
const size_t v210Bytes =
static_cast<size_t>(
@@ -1719,30 +2036,7 @@ int main(int argc, char* argv[])
}
if (config.logPerf)
{
++feedPerfStats[i].uploads;
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;
recordFeedUploadStats(i);
}
feedUploadNeeded[i] = true;
}
@@ -1849,8 +2143,8 @@ int main(int argc, char* argv[])
feeds[i]->v210Width(),
feeds[i]->v210Height(),
feeds[i]->v210Stride(),
TEXTURE_WIDTH,
TEXTURE_HEIGHT
feedTextureWidth,
feedTextureHeight
);
v210Decoder.recordOutputReadyForSampling(
@@ -1863,8 +2157,8 @@ int main(int argc, char* argv[])
recordFeedTextureUpload(
uploadCmdBuf,
feedTextures[i],
TEXTURE_WIDTH,
TEXTURE_HEIGHT
feedTextureWidth,
feedTextureHeight
);
}
}
@@ -2143,6 +2437,14 @@ int main(int argc, char* argv[])
<< (static_cast<double>(
framePerf.directV210PayloadCopyNs) /
1000000.0)
<< "ms v210WorkerRead="
<< (static_cast<double>(
framePerf.workerV210SourceReadNs) /
1000000.0)
<< "ms v210WorkerCopy="
<< (static_cast<double>(
framePerf.workerV210PayloadCopyNs) /
1000000.0)
<< "ms stageCopies="
<< framePerf.stageCopies
<< " v210Copies="
@@ -2202,6 +2504,14 @@ int main(int argc, char* argv[])
<< (static_cast<double>(
perfStats.directV210PayloadCopyNs) /
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="
<< (perfStats.uploadRecordTicks * invMs / frames)
<< "ms submitPresent="
@@ -2300,6 +2610,11 @@ int main(int argc, char* argv[])
// Cleanup
// ----------------------------------------
if (v210UploadWorker)
{
v210UploadWorker->stop();
}
vkDeviceWaitIdle(ctx.device());
ImGui_ImplVulkan_Shutdown();