Skip to content

Commit

Permalink
feat: add function for generating path for new topology (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog authored May 15, 2024
1 parent e918bf6 commit 842658f
Show file tree
Hide file tree
Showing 4 changed files with 382 additions and 35 deletions.
3 changes: 1 addition & 2 deletions src/windows/include/displaydevice/windows/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <map>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

namespace display_device {
Expand Down Expand Up @@ -41,7 +40,7 @@ namespace display_device {
* @brief Contains information about sources with identical adapter ids from matching paths.
*/
struct PathSourceIndexData {
std::unordered_map<UINT32, std::size_t> m_source_id_to_path_index; /**< Maps source ids to its index in the path list. */
std::map<UINT32, std::size_t> m_source_id_to_path_index; /**< Maps source ids to its index in the path list. */
LUID m_adapter_id {}; /**< Adapter id shared by all source ids. */
std::optional<UINT32> m_active_source; /**< Currently active source id. */
};
Expand Down
22 changes: 22 additions & 0 deletions src/windows/include/displaydevice/windows/winapiutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,26 @@ namespace display_device::win_utils {
*/
[[nodiscard]] PathSourceIndexDataMap
collectSourceDataForMatchingPaths(const WinApiLayerInterface &w_api, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths);

/**
* @brief Select the best possible paths to be used for the requested topology based on the data that is available to us.
*
* If the paths will be used for a completely new topology (Windows has never had it set), we need to take into
* account the source id availability per the adapter - duplicated displays must share the same source id
* (if they belong to the same adapter) and have different ids if they are not duplicated displays.
*
* There are limited amount of available ids (see comments in the code) so we will abort early if we are
* out of ids.
*
* The paths for a topology that already exists (Windows has set it at least once) does not have to follow
* the mentioned "source id" rule. Windows can simply ignore them (we will ask it later) and select
* paths that were previously configured (that might differ in source ids) based on the paths that we provide.
*
* @param new_topology Topology that we want to have in the end.
* @param path_source_data Collected source data from paths.
* @param paths Display paths that were used for collecting source data.
* @return A list of path that will make up new topology, or an empty list if function fails.
*/
[[nodiscard]] std::vector<DISPLAYCONFIG_PATH_INFO>
makePathsForNewTopology(const ActiveTopology &new_topology, const PathSourceIndexDataMap &path_source_data, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths);
} // namespace display_device::win_utils
123 changes: 123 additions & 0 deletions src/windows/winapiutils.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// header include
#include "displaydevice/windows/winapiutils.h"

// system includes
#include <unordered_set>

// local includes
#include "displaydevice/logging.h"

Expand All @@ -20,6 +23,21 @@ namespace {
operator!=(const LUID &lhs, const LUID &rhs) {
return lhs.HighPart != rhs.HighPart || lhs.LowPart != rhs.LowPart;
}

/**
* @brief Stringify adapter id.
* @param id Id to stringify.
* @return String representation of the id.
*
* EXAMPLES:
* ```cpp
* const bool id_string = to_string({ 12, 34 });
* ```
*/
std::string
toString(const LUID &id) {
return std::to_string(id.HighPart) + std::to_string(id.LowPart);
}
} // namespace

namespace display_device::win_utils {
Expand Down Expand Up @@ -250,4 +268,109 @@ namespace display_device::win_utils {
}
return path_data;
}

std::vector<DISPLAYCONFIG_PATH_INFO>
makePathsForNewTopology(const ActiveTopology &new_topology, const PathSourceIndexDataMap &path_source_data, const std::vector<DISPLAYCONFIG_PATH_INFO> &paths) {
std::vector<DISPLAYCONFIG_PATH_INFO> new_paths;

UINT32 group_id { 0 };
std::unordered_map<std::string, std::unordered_set<UINT32>> used_source_ids_per_adapter;
const auto is_source_id_already_used = [&used_source_ids_per_adapter](const LUID &adapter_id, UINT32 source_id) {
auto entry_it { used_source_ids_per_adapter.find(toString(adapter_id)) };
if (entry_it != std::end(used_source_ids_per_adapter)) {
return entry_it->second.contains(source_id);
}

return false;
};

for (const auto &group : new_topology) {
std::unordered_map<std::string, UINT32> used_source_ids_per_adapter_per_group;
const auto get_already_used_source_id_in_group = [&used_source_ids_per_adapter_per_group](const LUID &adapter_id) -> std::optional<UINT32> {
auto entry_it { used_source_ids_per_adapter_per_group.find(toString(adapter_id)) };
if (entry_it != std::end(used_source_ids_per_adapter_per_group)) {
return entry_it->second;
}

return std::nullopt;
};

for (const std::string &device_id : group) {
auto path_source_data_it { path_source_data.find(device_id) };
if (path_source_data_it == std::end(path_source_data)) {
DD_LOG(error) << "Device " << device_id << " does not exist in the available path source data!";
return {};
}

std::size_t selected_path_index {};
const auto &source_data { path_source_data_it->second };

const auto already_used_source_id { get_already_used_source_id_in_group(source_data.m_adapter_id) };
if (already_used_source_id) {
// Some device in the group is already using the source id, and we belong to the same adapter.
// This means we must also use the path with matching source id.
auto path_index_it { source_data.m_source_id_to_path_index.find(*already_used_source_id) };
if (path_index_it == std::end(source_data.m_source_id_to_path_index)) {
DD_LOG(error) << "Device " << device_id << " does not have a path with a source id " << *already_used_source_id << "!";
return {};
}

selected_path_index = path_index_it->second;
}
else {
// Here we want to select a path index that has the lowest index (the "best" of paths), but only
// if the source id is still free. Technically we should not need to find the lowest index, but that's
// what will match the Windows' behaviour the closest if we need to create new topology in the end.
std::optional<std::size_t> path_index_candidate;
UINT32 used_source_id {};
for (const auto [source_id, index] : source_data.m_source_id_to_path_index) {
if (is_source_id_already_used(source_data.m_adapter_id, source_id)) {
continue;
}

if (!path_index_candidate || index < *path_index_candidate) {
path_index_candidate = index;
used_source_id = source_id;
}
}

if (!path_index_candidate) {
// Apparently nvidia GPU can only render 4 different sources at a time (according to Google).
// However, it seems to be true only for physical connections as we also have virtual displays.
//
// Virtual displays have different adapter ids than the physical connection ones, but GPU still
// has to render them, so I don't know how this 4 source limitation makes sense then?
//
// In short, this arbitrary limitation should not affect virtual displays when the GPU is at its limit.
DD_LOG(error) << "Device " << device_id << " cannot be enabled as the adapter has no more free source ids (GPU limitation)!";
return {};
}

selected_path_index = *path_index_candidate;
used_source_ids_per_adapter[toString(source_data.m_adapter_id)].insert(used_source_id);
used_source_ids_per_adapter_per_group[toString(source_data.m_adapter_id)] = used_source_id;
}

if (selected_path_index >= paths.size()) {
DD_LOG(error) << "Selected path index " << selected_path_index << " is out of range! List size: " << paths.size();
return {};
}

auto selected_path { paths[selected_path_index] };

// All the indexes must be cleared and only the group id specified
win_utils::setSourceIndex(selected_path, std::nullopt);
win_utils::setTargetIndex(selected_path, std::nullopt);
win_utils::setDesktopIndex(selected_path, std::nullopt);
win_utils::setCloneGroupId(selected_path, group_id);
win_utils::setActive(selected_path); // We also need to mark it as active...

new_paths.push_back(selected_path);
}

group_id++;
}

return new_paths;
}
} // namespace display_device::win_utils
Loading

0 comments on commit 842658f

Please sign in to comment.