From f11db57d6891432901816ebe28c5070521a8e510 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Thu, 14 Nov 2024 16:42:51 +0000 Subject: [PATCH] Function to compute uniform blocks from DXIL unstructured control flow New files dxil_controlflow.{cpp,h} --- .../driver/shaders/dxil/dxil_controlflow.cpp | 383 ++++++++++++++++++ .../driver/shaders/dxil/dxil_controlflow.h | 33 ++ .../shaders/dxil/renderdoc_dxil.vcxproj | 2 + .../dxil/renderdoc_dxil.vcxproj.filters | 2 + 4 files changed, 420 insertions(+) create mode 100644 renderdoc/driver/shaders/dxil/dxil_controlflow.cpp create mode 100644 renderdoc/driver/shaders/dxil/dxil_controlflow.h diff --git a/renderdoc/driver/shaders/dxil/dxil_controlflow.cpp b/renderdoc/driver/shaders/dxil/dxil_controlflow.cpp new file mode 100644 index 000000000..6eb0116a8 --- /dev/null +++ b/renderdoc/driver/shaders/dxil/dxil_controlflow.cpp @@ -0,0 +1,383 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include + +#include "dxil_controlflow.h" + +/* + +Inputs are links of blocks : from -> to (can be forwards or backwards links) +Output is a list of uniform control flow blocks which all possible flows go through (not diverged) + +The algorithm is: + +1. Setup + * Compute all possible known blocks. + * For each block generate a list of "to" blocks from the input links + * Any block without links in the input are set to link to the end sentinel (PATH_END) + +2. Generate all possible paths + * Paths can terminate at the end block (PATH_END) + * Paths can also terminate at a block before the end, if that block has had all its possible paths +already computed + +3. Find Uniform Blocks + * Generate a list of path indexes for each block in the paths + * uniform block is defined by any block which appears in all paths + * all paths includes walking any paths linked at the end node of the path being walked +*/ + +namespace DXIL +{ +struct ControlFlow +{ +public: + ControlFlow(const rdcarray> &links); + void FindUniformBlocks(rdcarray &uniformBlocks); + +private: + typedef rdcarray BlockPath; + + bool TraceBlockFlow(const uint32_t from, BlockPath &path); + bool BlockInPath(uint32_t block, uint32_t pathIdx, uint32_t startIdx); + + const uint32_t PATH_END = ~0U; + + std::set m_Blocks; + std::map m_BlockLinks; + + std::map> m_BlockPathLinks; + std::set m_TracedBlocks; + std::set m_CheckedPaths; + rdcarray m_Paths; +}; + +ControlFlow::ControlFlow(const rdcarray> &links) +{ + // 1. Setup + // Compute all possible known blocks + for(const auto &link : links) + { + uint32_t from = link.first; + uint32_t to = link.second; + m_Blocks.insert(from); + m_Blocks.insert(to); + } + + // For each block a list of "to" blocks + for(const auto &link : links) + { + uint32_t from = link.first; + uint32_t to = link.second; + m_BlockLinks[from].push_back(to); + } + + // Any block without links in the input are set to link to the end sentinel (PATH_END) + for(uint32_t b : m_Blocks) + { + if(m_BlockLinks.count(b) == 0) + m_BlockLinks[b].push_back(PATH_END); + } + + // 2. Generate all possible paths + + // Paths can terminate at the end block (PATH_END) + // Paths can also terminate at a block before the end, if that block has had all its possible paths already computed + for(const auto &it : m_BlockLinks) + { + uint32_t from = it.first; + if(m_TracedBlocks.count(from) != 0) + continue; + BlockPath path; + path.push_back(from); + TraceBlockFlow(from, path); + } +} + +bool ControlFlow::TraceBlockFlow(const uint32_t from, BlockPath &path) +{ + if(m_BlockLinks.count(from) == 0) + { + m_Paths.push_back(path); + return true; + } + if(m_TracedBlocks.count(from) != 0) + { + m_Paths.push_back(path); + return true; + } + m_TracedBlocks.insert(from); + rdcarray newPath = path; + const BlockPath &gotos = m_BlockLinks.at(from); + for(uint32_t to : gotos) + { + newPath.push_back(to); + if(TraceBlockFlow(to, newPath)) + newPath = path; + } + return true; +} + +bool ControlFlow::BlockInPath(uint32_t block, uint32_t pathIdx, uint32_t startIdx) +{ + const rdcarray &path = m_Paths[pathIdx]; + if(path.size() == 0) + return false; + + // Check the current path + for(uint32_t i = startIdx; i < path.size(); ++i) + { + if(block == path[i]) + return true; + } + + m_CheckedPaths.insert(pathIdx); + uint32_t endNode = path[path.size() - 1]; + if(endNode == PATH_END) + return false; + + // Check any paths linked to by the end node of the current path + const rdcarray &childPathsToCheck = m_BlockPathLinks[endNode]; + for(uint32_t childPathIdx : childPathsToCheck) + { + if(m_CheckedPaths.count(childPathIdx) != 0) + continue; + + m_CheckedPaths.insert(childPathIdx); + const rdcarray &childPath = m_Paths[childPathIdx]; + uint32_t childPartStartIdx = ~0U; + for(childPartStartIdx = 0; childPartStartIdx < childPath.size(); ++childPartStartIdx) + { + if(childPath[childPartStartIdx] == endNode) + break; + } + if(!BlockInPath(block, childPathIdx, childPartStartIdx)) + return false; + } + return true; +} + +void ControlFlow::FindUniformBlocks(rdcarray &uniformBlocks) +{ + // 3. Find Uniform Blocks + for(uint32_t b : m_Blocks) + m_BlockPathLinks[b].clear(); + + // Generate a list of path indexes for each block in the paths + for(uint32_t pathIdx = 0; pathIdx < m_Paths.size(); ++pathIdx) + { + for(uint32_t block : m_Paths[pathIdx]) + { + if(block == PATH_END) + break; + m_BlockPathLinks[block].push_back(pathIdx); + } + } + + uniformBlocks.clear(); + + // A uniform block is defined by any block which appears in all paths + // all paths includes walking any paths linked at the end node of the path being walked + for(uint32_t block : m_Blocks) + { + bool uniform = true; + for(uint32_t pathIdx = 0; pathIdx < m_Paths.size(); ++pathIdx) + { + m_CheckedPaths.clear(); + // BlockInPath will also check all paths linked to from the end node of the path + if(!BlockInPath(block, pathIdx, 0)) + { + uniform = false; + break; + } + } + if(uniform) + uniformBlocks.push_back(block); + } +} + +void FindUniformBlocks(const rdcarray &links, rdcarray &uniformBlocks) +{ + ControlFlow controlFlow(links); + + controlFlow.FindUniformBlocks(uniformBlocks); +} + +}; // namespace DXIL + +#if ENABLED(ENABLE_UNIT_TESTS) + +#include +#include "catch/catch.hpp" + +using namespace DXIL; + +TEST_CASE("DXIL Control Flow", "[dxil]") +{ + SECTION("FindUniformBlocks") + { + rdcarray outputs; + { + // Degenerate case + rdcarray inputs; + DXIL::FindUniformBlocks(inputs, outputs); + REQUIRE(0 == outputs.count()); + } + { + // Only uniform flow is the start and end + // 0 -> 1 + rdcarray inputs; + inputs.push_back({0, 1}); + DXIL::FindUniformBlocks(inputs, outputs); + REQUIRE(2 == outputs.count()); + REQUIRE(outputs.contains(0U)); + REQUIRE(outputs.contains(1U)); + } + + { + // Single uniform flow between start and end + // 0 -> 1 -> 3 + // 0 -> 2 -> 3 + // 3 -> 4 + rdcarray inputs; + inputs.push_back({0, 1}); + inputs.push_back({1, 3}); + inputs.push_back({0, 2}); + inputs.push_back({2, 3}); + inputs.push_back({3, 4}); + DXIL::FindUniformBlocks(inputs, outputs); + REQUIRE(3 == outputs.count()); + REQUIRE(outputs.contains(0U)); + REQUIRE(outputs.contains(3U)); + REQUIRE(outputs.contains(4U)); + } + + { + // Finite Loop (3 -> 4 -> 5 -> 3) + // 0 -> 1 -> 3 + // 0 -> 2 -> 3 + // 3 -> 4 -> 5 + // 4 -> 6 + // 5 -> 3 + // 5 -> 6 + rdcarray inputs; + inputs.push_back({0, 1}); + inputs.push_back({1, 3}); + inputs.push_back({0, 2}); + inputs.push_back({2, 3}); + inputs.push_back({3, 4}); + inputs.push_back({4, 5}); + inputs.push_back({4, 6}); + inputs.push_back({5, 3}); + inputs.push_back({5, 6}); + DXIL::FindUniformBlocks(inputs, outputs); + REQUIRE(4 == outputs.count()); + REQUIRE(outputs.contains(0U)); + REQUIRE(outputs.contains(3U)); + REQUIRE(outputs.contains(4U)); + REQUIRE(outputs.contains(6U)); + } + + { + // Infinite loop which never converges (3 -> 4 -> 3) + // 0 -> 1 -> 3 + // 0 -> 2 -> 3 + // 3 -> 4 + // 4 -> 3 + // 1 -> 6 + // 2 -> 6 + rdcarray inputs; + inputs.push_back({0, 1}); + inputs.push_back({1, 3}); + inputs.push_back({0, 2}); + inputs.push_back({2, 3}); + inputs.push_back({3, 4}); + inputs.push_back({4, 3}); + inputs.push_back({1, 6}); + inputs.push_back({2, 6}); + DXIL::FindUniformBlocks(inputs, outputs); + REQUIRE(2 == outputs.count()); + REQUIRE(outputs.contains(0U)); + REQUIRE(outputs.contains(6U)); + } + + { + // Complex case with multiple loops + rdcarray inputs; + inputs.push_back({0, 1}); + inputs.push_back({0, 2}); + inputs.push_back({2, 3}); + inputs.push_back({1, 3}); + inputs.push_back({3, 4}); + inputs.push_back({4, 5}); + inputs.push_back({3, 5}); + inputs.push_back({5, 6}); + inputs.push_back({9, 7}); + inputs.push_back({6, 7}); + inputs.push_back({7, 8}); + inputs.push_back({7, 9}); + inputs.push_back({9, 10}); + inputs.push_back({10, 11}); + inputs.push_back({8, 11}); + inputs.push_back({5, 11}); + inputs.push_back({11, 12}); + inputs.push_back({15, 13}); + inputs.push_back({12, 13}); + inputs.push_back({13, 14}); + inputs.push_back({13, 15}); + inputs.push_back({15, 16}); + inputs.push_back({16, 17}); + inputs.push_back({14, 17}); + inputs.push_back({11, 17}); + inputs.push_back({17, 18}); + inputs.push_back({18, 19}); + inputs.push_back({17, 19}); + inputs.push_back({19, 20}); + inputs.push_back({20, 21}); + inputs.push_back({19, 21}); + inputs.push_back({21, 22}); + inputs.push_back({22, 23}); + inputs.push_back({22, 24}); + inputs.push_back({24, 25}); + inputs.push_back({25, 26}); + inputs.push_back({24, 26}); + inputs.push_back({23, 26}); + inputs.push_back({21, 26}); + DXIL::FindUniformBlocks(inputs, outputs); + REQUIRE(8 == outputs.count()); + REQUIRE(outputs.contains(0U)); + REQUIRE(outputs.contains(3U)); + REQUIRE(outputs.contains(5U)); + REQUIRE(outputs.contains(11U)); + REQUIRE(outputs.contains(17U)); + REQUIRE(outputs.contains(19U)); + REQUIRE(outputs.contains(21U)); + REQUIRE(outputs.contains(26U)); + } + }; +}; + +#endif // ENABLED(ENABLE_UNIT_TESTS) diff --git a/renderdoc/driver/shaders/dxil/dxil_controlflow.h b/renderdoc/driver/shaders/dxil/dxil_controlflow.h new file mode 100644 index 000000000..cb6108a1a --- /dev/null +++ b/renderdoc/driver/shaders/dxil/dxil_controlflow.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2024 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#pragma once + +namespace DXIL +{ +typedef rdcpair BlockLink; + +void FindUniformBlocks(const rdcarray &links, rdcarray &uniformBlocks); + +}; // namespace DXIL diff --git a/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj b/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj index c025c285c..ccdfc2d89 100644 --- a/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj +++ b/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj @@ -104,6 +104,7 @@ + @@ -120,6 +121,7 @@ + diff --git a/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj.filters b/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj.filters index 5e027355e..8083d0ef7 100644 --- a/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj.filters +++ b/renderdoc/driver/shaders/dxil/renderdoc_dxil.vcxproj.filters @@ -15,6 +15,7 @@ + @@ -31,6 +32,7 @@ +