blob: 85808e0e83f9c603c952051675374eb5d9282100 [file] [edit]
//===- SemanticSignaturePacking.cpp - HLSL signature packing helpers -----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file This file implements helpers for packing HLSL semantic signatures.
///
//===----------------------------------------------------------------------===//
#include "llvm/Frontend/HLSL/SemanticSignaturePacking.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/bit.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <limits>
#include <optional>
using namespace llvm;
using namespace llvm::hlsl;
char SignaturePackingError::ID;
namespace {
// The range of rows covered by a dynamically indexable element. Only an
// element that covers multiple rows is dynamically indexable, so a single-row
// element has an empty range.
struct IndexedRowRange {
uint8_t Begin = 0;
uint8_t End = 0;
static IndexedRowRange of(unsigned StartRow, unsigned RowCount) {
if (RowCount < 2)
return {};
return {static_cast<uint8_t>(StartRow),
static_cast<uint8_t>(StartRow + RowCount)};
}
bool isEmpty() const { return Begin == End; }
// An empty range is contained by every range.
bool contains(IndexedRowRange Other) const {
return Other.isEmpty() || (Begin <= Other.Begin && Other.End <= End);
}
IndexedRowRange unionWith(IndexedRowRange Other) const {
if (isEmpty())
return Other;
if (Other.isEmpty())
return *this;
return {std::min(Begin, Other.Begin), std::max(End, Other.End)};
}
bool operator==(IndexedRowRange Other) const {
return Begin == Other.Begin && End == Other.End;
}
};
static_assert(SemanticInterpretation::Arbitrary < SemanticInterpretation::SV &&
SemanticInterpretation::SV < SemanticInterpretation::SGV &&
SemanticInterpretation::SGV <
SemanticInterpretation::ClipCull &&
SemanticInterpretation::ClipCull <
SemanticInterpretation::TessFactor,
"semantic interpretations must be in component packing order");
struct SignatureRow {
uint8_t OccupiedColumns = 0;
IndexedRowRange IndexedRange;
bool IndexedRangeFixed = false;
unsigned ComponentWidth = 0;
dxbc::PSV::InterpolationMode InterpMode =
dxbc::PSV::InterpolationMode::Undefined;
SemanticInterpretation RightmostInterpretation =
SemanticInterpretation::Arbitrary;
};
using SignatureRows = std::array<SignatureRow, MaxSignatureRows>;
struct ElementPlacement {
unsigned Rows;
unsigned Cols;
unsigned ComponentWidth;
dxbc::PSV::InterpolationMode InterpMode;
SemanticInterpretation Interpretation;
};
struct ElementLocation {
uint32_t Row = UnallocatedRow;
uint8_t Col = UnallocatedCol;
};
struct OptimizedClipCullElement {
unsigned Index;
ElementPlacement Placement;
ElementLocation Location;
};
// Clip/cull elements are first packed into an independent two-row grid. Each
// row used in that grid maps to a whole reserved row in the signature.
struct ClipCullState {
std::array<SignatureRow, MaxClipCullRows> Rows;
std::array<unsigned, MaxClipCullRows> SignatureRows = {UnallocatedRow,
UnallocatedRow};
unsigned RowsUsed = 0;
};
enum class PackingGroup : unsigned {
FullRegister,
IndexedTessFactor,
Arbitrary,
SystemValue,
ClipCull,
SystemGenerated,
NotAllocated,
};
} // namespace
static uint8_t getStartColumn(uint8_t ColumnMask) {
assert(ColumnMask != 0 && "expected at least one occupied column");
return countr_zero(ColumnMask);
}
static PackingGroup
getOptimizedPackingGroup(const SemanticSignatureElement &Element,
Triple::EnvironmentType ShaderStage, IOType IOTy) {
const SemanticInterpretation Interpretation =
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
assert((Interpretation != SemanticInterpretation::Invalid &&
Interpretation != SemanticInterpretation::Target) &&
"unexpected semantic interpretation for optimized packing, "
"should have been diagnosed by Sema");
if (Element.Cols == MaxSignatureCols &&
(Interpretation == SemanticInterpretation::Arbitrary ||
Interpretation == SemanticInterpretation::SV))
return PackingGroup::FullRegister;
if (Interpretation == SemanticInterpretation::TessFactor && Element.Rows > 1)
return PackingGroup::IndexedTessFactor;
switch (Interpretation) {
case SemanticInterpretation::Arbitrary:
return PackingGroup::Arbitrary;
case SemanticInterpretation::SV:
case SemanticInterpretation::TessFactor:
return PackingGroup::SystemValue;
case SemanticInterpretation::ClipCull:
return PackingGroup::ClipCull;
case SemanticInterpretation::SGV:
return PackingGroup::SystemGenerated;
case SemanticInterpretation::NotAllocated:
return PackingGroup::NotAllocated;
case SemanticInterpretation::Invalid:
case SemanticInterpretation::Target:
break;
}
llvm_unreachable("unexpected semantic interpretation for optimized packing");
}
static unsigned getComponentWidth(dxil::ElementType ComponentType,
bool UseNative16BitTypes) {
assert(ComponentType != dxil::ElementType::I64 &&
ComponentType != dxil::ElementType::U64 &&
ComponentType != dxil::ElementType::F64 &&
ComponentType != dxil::ElementType::SNormF64 &&
ComponentType != dxil::ElementType::UNormF64 &&
"64-bit types cannot be used in a signature");
switch (ComponentType) {
case dxil::ElementType::F16:
case dxil::ElementType::I16:
case dxil::ElementType::U16:
case dxil::ElementType::SNormF16:
case dxil::ElementType::UNormF16:
// Without native 16-bit types these are min-precision types that occupy a
// whole 32-bit component.
return UseNative16BitTypes ? 16 : 32;
default:
// A boolean is loaded and stored as a 32-bit value.
return 32;
}
}
static ElementPlacement
getElementPlacement(const SemanticSignatureElement &Element,
SemanticInterpretation Interpretation,
bool UseNative16BitTypes) {
// Only indexed tessellation factors need the reserved last column.
if (Interpretation == SemanticInterpretation::TessFactor && Element.Rows == 1)
Interpretation = SemanticInterpretation::SV;
return {Element.Rows, Element.Cols,
getComponentWidth(Element.CompType, UseNative16BitTypes),
Element.InterpMode, Interpretation};
}
static SemanticInterpretation
getComponentOrder(SemanticInterpretation Interpretation) {
// Clip/cull values have system-value component ordering, but may be indexed.
return Interpretation == SemanticInterpretation::ClipCull
? SemanticInterpretation::SV
: Interpretation;
}
// Returns whether Placement may be co-packed into a Row that it covers, where
// IndexedRange is the range of rows that it is dynamically indexed over.
static bool canCoPack(const SignatureRow &Row,
const ElementPlacement &Placement,
IndexedRowRange IndexedRange) {
const bool IsSystemValue =
Placement.Interpretation == SemanticInterpretation::SV ||
Placement.Interpretation == SemanticInterpretation::SGV;
// A system value is never dynamically indexable, so it cannot be placed in a
// row that is.
if (IsSystemValue && !Row.IndexedRange.isEmpty())
return false;
// A row whose indexed range is fixed only accepts elements that are indexed
// within that range.
if (Row.IndexedRangeFixed && !Row.IndexedRange.contains(IndexedRange))
return false;
// A tess factor fixes the indexed range of the rows it is reserved in, so it
// may only extend the range that those rows already have.
if (Placement.Interpretation == SemanticInterpretation::TessFactor &&
!IndexedRange.contains(Row.IndexedRange))
return false;
if (Row.OccupiedColumns && Row.ComponentWidth != Placement.ComponentWidth)
return false;
if (Row.InterpMode != dxbc::PSV::InterpolationMode::Undefined &&
Row.InterpMode != Placement.InterpMode)
return false;
// Do not append an earlier semantic category after a later one in a row.
// Indexed tess factors are reserved in the last column, so arbitrary values
// may still fill the columns to their left without violating that ordering.
if (Row.OccupiedColumns &&
getComponentOrder(Placement.Interpretation) <
getComponentOrder(Row.RightmostInterpretation) &&
!(Placement.Interpretation == SemanticInterpretation::Arbitrary &&
Row.RightmostInterpretation == SemanticInterpretation::TessFactor))
return false;
return true;
}
// Returns the columns that Placement would occupy if it was placed at StartRow,
// or nullopt if it cannot be placed there.
static std::optional<uint8_t> canPlaceAt(ArrayRef<SignatureRow> Rows,
unsigned StartRow,
const ElementPlacement &Placement) {
if (StartRow >= Rows.size() || Placement.Rows > Rows.size() - StartRow)
return std::nullopt;
const IndexedRowRange IndexedRange =
IndexedRowRange::of(StartRow, Placement.Rows);
uint8_t OccupiedColumns = 0;
for (unsigned ElementRow = 0; ElementRow != Placement.Rows; ++ElementRow) {
const SignatureRow &Row = Rows[StartRow + ElementRow];
if (!canCoPack(Row, Placement, IndexedRange))
return std::nullopt;
OccupiedColumns |= Row.OccupiedColumns;
}
// An indexed tess factor is reserved in the last column so that other
// elements can still be co-packed into the rows that it covers.
if (Placement.Interpretation == SemanticInterpretation::TessFactor) {
constexpr uint8_t LastColumn = 1U << (MaxSignatureCols - 1);
if (Placement.Cols != 1 || (OccupiedColumns & LastColumn))
return std::nullopt;
return LastColumn;
}
for (unsigned StartCol = 0; StartCol + Placement.Cols <= MaxSignatureCols;
++StartCol) {
const uint8_t ColumnMask = static_cast<uint8_t>(
((1U << Placement.Cols) - 1U) << static_cast<unsigned>(StartCol));
if (!(OccupiedColumns & ColumnMask))
return ColumnMask;
}
return std::nullopt;
}
static void placeRowsAt(MutableArrayRef<SignatureRow> Rows, unsigned StartRow,
const ElementPlacement &Placement, uint8_t ColumnMask) {
const IndexedRowRange IndexedRange =
IndexedRowRange::of(StartRow, Placement.Rows);
for (unsigned ElementRow = 0; ElementRow != Placement.Rows; ++ElementRow) {
SignatureRow &Row = Rows[StartRow + ElementRow];
assert(!(Row.OccupiedColumns & ColumnMask) &&
"cannot overlap signature elements");
const uint8_t PreviousOccupiedColumns = Row.OccupiedColumns;
if (!PreviousOccupiedColumns)
Row.ComponentWidth = Placement.ComponentWidth;
Row.OccupiedColumns |= ColumnMask;
if (Row.InterpMode == dxbc::PSV::InterpolationMode::Undefined)
Row.InterpMode = Placement.InterpMode;
// Non-overlapping masks compare according to their rightmost set bit.
if (!PreviousOccupiedColumns || ColumnMask > PreviousOccupiedColumns)
Row.RightmostInterpretation = Placement.Interpretation;
Row.IndexedRange = Row.IndexedRange.unionWith(IndexedRange);
if (Placement.Interpretation == SemanticInterpretation::SV ||
Placement.Interpretation == SemanticInterpretation::SGV ||
Placement.Interpretation == SemanticInterpretation::TessFactor) {
assert(Row.IndexedRange == IndexedRange && "incompatible index range");
Row.IndexedRangeFixed = true;
}
}
}
static void placeAt(MutableArrayRef<SignatureRow> Rows, unsigned StartRow,
const ElementPlacement &Placement, uint8_t ColumnMask,
ElementLocation &Location) {
placeRowsAt(Rows, StartRow, Placement, ColumnMask);
Location.Row = StartRow;
Location.Col = getStartColumn(ColumnMask);
}
static bool prefixPackElement(ElementLocation &Location,
MutableArrayRef<SignatureRow> Rows,
const ElementPlacement &Placement) {
for (unsigned StartRow = 0; StartRow != Rows.size(); ++StartRow) {
std::optional<uint8_t> ColumnMask = canPlaceAt(Rows, StartRow, Placement);
if (!ColumnMask)
continue;
placeAt(Rows, StartRow, Placement, *ColumnMask, Location);
return true;
}
return false;
}
// A clip/cull grid row is backed by a whole reserved signature row.
static ElementPlacement
getClipCullReservation(const ElementPlacement &Placement, unsigned RowCount) {
ElementPlacement Reservation = Placement;
Reservation.Rows = RowCount;
Reservation.Cols = MaxSignatureCols;
return Reservation;
}
static bool reserveClipCullRows(MutableArrayRef<SignatureRow> Rows,
unsigned StartRow,
const ElementPlacement &Reservation) {
std::optional<uint8_t> ColumnMask = canPlaceAt(Rows, StartRow, Reservation);
if (!ColumnMask)
return false;
placeRowsAt(Rows, StartRow, Reservation, *ColumnMask);
return true;
}
static std::optional<unsigned>
reserveNextClipCullRows(MutableArrayRef<SignatureRow> Rows,
const ElementPlacement &Reservation) {
for (unsigned StartRow = 0; StartRow != Rows.size(); ++StartRow)
if (reserveClipCullRows(Rows, StartRow, Reservation))
return StartRow;
return std::nullopt;
}
// Reserves the whole signature rows that back the clip/cull grid rows that the
// element is packed into. Existing rows cannot be moved without breaking
// prefix stability, so an indexed element requires them to be adjacent.
static std::optional<SignaturePackingError::ErrorKind>
reserveClipCullSignatureRows(MutableArrayRef<SignatureRow> SignatureRows,
ClipCullState &State,
const ElementPlacement &Placement,
unsigned NewRowsUsed) {
if (Placement.Rows == 1) {
const ElementPlacement Reservation = getClipCullReservation(Placement, 1);
for (unsigned Row = State.RowsUsed; Row < NewRowsUsed; ++Row) {
std::optional<unsigned> StartRow =
reserveNextClipCullRows(SignatureRows, Reservation);
if (!StartRow)
return SignaturePackingError::SignatureOverflow;
State.SignatureRows[Row] = *StartRow;
}
return std::nullopt;
}
if (State.RowsUsed == 0) {
std::optional<unsigned> StartRow = reserveNextClipCullRows(
SignatureRows, getClipCullReservation(Placement, MaxClipCullRows));
if (!StartRow)
return SignaturePackingError::SignatureOverflow;
State.SignatureRows[0] = *StartRow;
State.SignatureRows[1] = *StartRow + 1;
return std::nullopt;
}
if (State.RowsUsed == 1) {
const unsigned StartRow = State.SignatureRows[0] + 1;
if (StartRow >= SignatureRows.size())
return SignaturePackingError::SignatureOverflow;
if (!reserveClipCullRows(SignatureRows, StartRow,
getClipCullReservation(Placement, 1)))
return SignaturePackingError::ClipCullNotAdjacent;
State.SignatureRows[1] = StartRow;
return std::nullopt;
}
if (State.SignatureRows[0] + 1 != State.SignatureRows[1])
return SignaturePackingError::ClipCullNotAdjacent;
return std::nullopt;
}
static std::optional<SignaturePackingError::ErrorKind>
packClipCullElement(ElementLocation &Location,
MutableArrayRef<SignatureRow> SignatureRows,
ClipCullState &State, const ElementPlacement &Placement) {
std::optional<uint8_t> ColumnMask;
unsigned ClipCullStartRow = 0;
while (ClipCullStartRow + Placement.Rows <= MaxClipCullRows) {
ColumnMask = canPlaceAt(State.Rows, ClipCullStartRow, Placement);
if (ColumnMask)
break;
++ClipCullStartRow;
}
if (!ColumnMask)
return SignaturePackingError::ClipCullOverflow;
const unsigned NewRowsUsed = ClipCullStartRow + Placement.Rows;
if (std::optional<SignaturePackingError::ErrorKind> Kind =
reserveClipCullSignatureRows(SignatureRows, State, Placement,
NewRowsUsed))
return Kind;
placeRowsAt(State.Rows, ClipCullStartRow, Placement, *ColumnMask);
State.RowsUsed = std::max(State.RowsUsed, NewRowsUsed);
Location.Row = State.SignatureRows[ClipCullStartRow];
Location.Col = getStartColumn(*ColumnMask);
return std::nullopt;
}
// Work only on scratch rows and locations. The caller commits the entire
// clip/cull phase after every stream succeeds.
static Error
packOptimizedClipCullStream(MutableArrayRef<OptimizedClipCullElement> Elements,
MutableArrayRef<SignatureRow> Rows) {
if (Elements.empty())
return Error::success();
std::array<SignatureRow, MaxClipCullRows> LocalRows;
bool HasIndexed = false;
for (auto &Element : Elements) {
if (!prefixPackElement(Element.Location, LocalRows, Element.Placement))
return make_error<SignaturePackingError>(
SignaturePackingError::ClipCullOverflow, Element.Index);
HasIndexed |= Element.Placement.Rows > 1;
}
if (!HasIndexed) {
// Keep single-row elements grouped into at most two rows, rather than
// scattering individual distances across unrelated signature gaps.
std::array<ElementLocation, MaxClipCullRows> Destinations;
for (unsigned Row = 0; Row != MaxClipCullRows; ++Row) {
auto First = llvm::find_if(Elements, [Row](const auto &Element) {
return Element.Location.Row == Row;
});
if (First == Elements.end())
continue;
ElementPlacement Bundle = First->Placement;
Bundle.Cols = popcount(LocalRows[Row].OccupiedColumns);
if (!prefixPackElement(Destinations[Row], Rows, Bundle))
return make_error<SignaturePackingError>(
SignaturePackingError::SignatureOverflow, First->Index);
// Later elements can establish an initially undefined interpolation mode.
Rows[Destinations[Row].Row].InterpMode = LocalRows[Row].InterpMode;
}
for (auto &Element : Elements) {
ElementLocation Destination = Destinations[Element.Location.Row];
Element.Location = {
Destination.Row,
static_cast<uint8_t>(Destination.Col + Element.Location.Col)};
}
return Error::success();
}
// Try the whole indexed group in each adjacent pair. Preserve absolute row
// coordinates for existing indexed ranges, even those extending beyond it.
for (unsigned Row = 0; Row + MaxClipCullRows <= Rows.size(); ++Row) {
SmallVector<SignatureRow, MaxSignatureRows> CandidateRows(Rows.begin(),
Rows.end());
bool Fits = true;
for (auto &Element : Elements) {
Element.Location = {};
for (unsigned Start = Row;
Start + Element.Placement.Rows <= Row + MaxClipCullRows; ++Start) {
if (auto Mask = canPlaceAt(CandidateRows, Start, Element.Placement)) {
placeAt(CandidateRows, Start, Element.Placement, *Mask,
Element.Location);
break;
}
}
if (Element.Location.Row == UnallocatedRow) {
Fits = false;
break;
}
}
if (Fits) {
llvm::copy(ArrayRef(CandidateRows).slice(Row, MaxClipCullRows),
Rows.begin() + Row);
return Error::success();
}
}
// No element alone necessarily caused this failure; identify the group.
return make_error<SignaturePackingError>(
SignaturePackingError::SignatureOverflow, Elements.front().Index);
}
static Expected<unsigned>
packOptimizedClipCull(MutableArrayRef<SemanticSignatureElement> Elements,
ArrayRef<unsigned> Order,
MutableArrayRef<SignatureRows> Rows,
bool UseNative16BitTypes) {
if (Order.empty())
return 0;
SmallVector<SmallVector<OptimizedClipCullElement>, MaxGeometryStreams>
Streams(Rows.size());
for (unsigned Index : Order) {
const auto &Element = Elements[Index];
assert(Element.StartRow == UnallocatedRow &&
Element.StartCol == UnallocatedCol && "already allocated?");
assert(Element.Rows > 0 && "signature element must have at least one row");
assert(Element.Cols > 0 && Element.Cols <= MaxSignatureCols &&
"signature element must have between 1 and 4 columns");
if (Element.GSStream >= Rows.size())
return make_error<SignaturePackingError>(
SignaturePackingError::InvalidGeometryStream, Index);
Streams[Element.GSStream].push_back(
{Index,
getElementPlacement(Element, SemanticInterpretation::ClipCull,
UseNative16BitTypes),
{}});
}
SmallVector<SignatureRows, MaxGeometryStreams> CandidateRows(Rows.begin(),
Rows.end());
for (unsigned Stream = 0; Stream != Streams.size(); ++Stream)
if (Error Err =
packOptimizedClipCullStream(Streams[Stream], CandidateRows[Stream]))
return std::move(Err);
// Publish only after every stream succeeds. No rollback or partial-prefix
// recovery is needed, and preceding non-clip/cull allocations remain intact.
llvm::copy(CandidateRows, Rows.begin());
unsigned NumRows = 0;
for (const auto &Stream : Streams)
for (const auto &Element : Stream) {
Elements[Element.Index].StartRow = Element.Location.Row;
Elements[Element.Index].StartCol = Element.Location.Col;
NumRows =
std::max(NumRows, Element.Location.Row + Element.Placement.Rows);
}
return NumRows;
}
void SignaturePackingError::log(raw_ostream &OS) const {
switch (Kind) {
case SignatureOverflow:
OS << "signature elements do not fit in " << MaxSignatureRows << " rows";
break;
case SemanticIndexOutOfRange:
OS << "semantic index must be less than " << MaxSignatureRows;
break;
case ClipCullOverflow:
OS << "clip/cull elements do not fit in " << MaxClipCullRows << " rows";
break;
case ClipCullNotAdjacent:
OS << "indexed clip/cull elements require adjacent signature rows";
break;
case InvalidGeometryStream:
OS << "signature element has an invalid geometry stream: expected an index "
"less than "
<< MaxGeometryStreams << " for geometry outputs, or zero otherwise";
break;
}
OS << " (element " << ElementIndex << ")";
}
Expected<unsigned> llvm::hlsl::packSignatureStacked(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy) {
assert(ShaderStage == Triple::Vertex && IOTy == IOType::In &&
"stacked packing is only valid for a vertex shader input signature");
unsigned NextRow = 0;
for (auto &&[Index, Element] : enumerate(Elements)) {
assert(Element.StartRow == UnallocatedRow &&
Element.StartCol == UnallocatedCol && "already allocated?");
assert(Element.Rows > 0 && "signature element must have at least one row");
assert(Element.Cols > 0 && Element.Cols <= MaxSignatureCols &&
"signature element must have between 1 and 4 columns");
SemanticInterpretation Interpretation =
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
if (Interpretation == SemanticInterpretation::NotAllocated)
continue;
assert((Interpretation == SemanticInterpretation::Arbitrary ||
Interpretation == SemanticInterpretation::SV ||
Interpretation == SemanticInterpretation::SGV) &&
"unexpected semantic interpretation for stacked packing, should "
"have been diagnosed by Sema");
if (Element.Rows > MaxSignatureRows - NextRow)
return make_error<SignaturePackingError>(
SignaturePackingError::SignatureOverflow,
static_cast<unsigned>(Index));
Element.StartRow = NextRow;
Element.StartCol = 0;
NextRow += Element.Rows;
}
return NextRow;
}
template <typename IndexRange>
static Expected<unsigned> packSignatureInOrder(
MutableArrayRef<SemanticSignatureElement> Elements, const IndexRange &Order,
Triple::EnvironmentType ShaderStage, IOType IOTy, bool UseNative16BitTypes,
MutableArrayRef<SignatureRows> Rows) {
SmallVector<ClipCullState, 1> ClipCullStates(Rows.size());
unsigned NumRows = 0;
for (unsigned Index : Order) {
const SemanticSignatureElement &Element = Elements[Index];
assert(Element.StartRow == UnallocatedRow &&
Element.StartCol == UnallocatedCol && "already allocated?");
assert(Element.Rows > 0 && "signature element must have at least one row");
assert(Element.Cols > 0 && Element.Cols <= MaxSignatureCols &&
"signature element must have between 1 and 4 columns");
if (Element.GSStream >= Rows.size())
return make_error<SignaturePackingError>(
SignaturePackingError::InvalidGeometryStream,
static_cast<unsigned>(Index));
SemanticInterpretation Interpretation =
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
if (Interpretation == SemanticInterpretation::NotAllocated)
continue;
assert((Interpretation == SemanticInterpretation::Arbitrary ||
Interpretation == SemanticInterpretation::SV ||
Interpretation == SemanticInterpretation::SGV ||
Interpretation == SemanticInterpretation::ClipCull ||
Interpretation == SemanticInterpretation::TessFactor) &&
"unexpected semantic interpretation for prefix-stable packing, "
"should have been diagnosed by Sema");
const ElementPlacement Placement =
getElementPlacement(Element, Interpretation, UseNative16BitTypes);
const unsigned StreamIndex = Element.GSStream;
MutableArrayRef<SignatureRow> StreamRows = Rows[StreamIndex];
ElementLocation Location;
if (Interpretation == SemanticInterpretation::ClipCull) {
if (std::optional<SignaturePackingError::ErrorKind> Kind =
packClipCullElement(Location, StreamRows,
ClipCullStates[StreamIndex], Placement))
return make_error<SignaturePackingError>(*Kind, Index);
} else if (!prefixPackElement(Location, StreamRows, Placement)) {
return make_error<SignaturePackingError>(
SignaturePackingError::SignatureOverflow,
static_cast<unsigned>(Index));
}
Elements[Index].StartRow = Location.Row;
Elements[Index].StartCol = Location.Col;
NumRows = std::max(NumRows, Location.Row + Element.Rows);
}
return NumRows;
}
Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
bool UseNative16BitTypes) {
assert(!(ShaderStage == Triple::Vertex && IOTy == IOType::In) &&
!(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
"prefix-stable packing is not valid for vertex inputs or pixel "
"outputs");
const unsigned StreamCount =
ShaderStage == Triple::Geometry && IOTy == IOType::Out
? MaxGeometryStreams
: 1;
SmallVector<SignatureRows, 1> Rows(StreamCount);
return packSignatureInOrder(Elements, llvm::seq<unsigned>(0, Elements.size()),
ShaderStage, IOTy, UseNative16BitTypes, Rows);
}
Expected<unsigned> llvm::hlsl::packSignatureIndexed(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy) {
assert(ShaderStage == Triple::Pixel && IOTy == IOType::Out &&
"indexed packing is only valid for a pixel shader output signature");
static_assert(MaxSignatureRows <= std::numeric_limits<uint32_t>::digits,
"row allocation mask is too small");
[[maybe_unused]] uint32_t AllocatedRows = 0;
unsigned NumRows = 0;
for (auto &&[Index, Element] : enumerate(Elements)) {
assert(Element.StartRow == UnallocatedRow &&
Element.StartCol == UnallocatedCol && "already allocated?");
assert(Element.Rows > 0 && "signature element must have at least one row");
assert(Element.Cols > 0 && Element.Cols <= MaxSignatureCols &&
"signature element must have between 1 and 4 columns");
SemanticInterpretation Interpretation =
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
if (Interpretation == SemanticInterpretation::NotAllocated)
continue;
assert(Interpretation == SemanticInterpretation::Target &&
"unexpected semantic interpretation for indexed packing, should "
"have been diagnosed by Sema");
assert(Element.Rows == 1 && Element.SemanticIndices.size() == 1 &&
"target elements must occupy one semantic row");
const uint32_t Row = Element.SemanticIndices.front();
if (Row >= MaxSignatureRows)
return make_error<SignaturePackingError>(
SignaturePackingError::SemanticIndexOutOfRange,
static_cast<unsigned>(Index));
const uint32_t RowMask = uint32_t{1} << Row;
assert(!(AllocatedRows & RowMask) &&
"target semantic indices must be unique, verified in SemaHLSL");
AllocatedRows |= RowMask;
Element.StartRow = Row;
Element.StartCol = 0;
NumRows = std::max(NumRows, Row + 1);
}
return NumRows;
}
Expected<unsigned> llvm::hlsl::packSignatureOptimized(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
bool UseNative16BitTypes) {
assert(!(ShaderStage == Triple::Vertex && IOTy == IOType::In) &&
!(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
"optimized packing is not valid for vertex inputs or pixel outputs");
struct SortKey {
PackingGroup Group;
dxbc::PSV::InterpolationMode InterpMode;
uint32_t Rows;
uint8_t Cols;
uint32_t SigId;
unsigned OriginalIndex;
};
SmallVector<SortKey> SortedKeys;
SortedKeys.reserve(Elements.size());
for (auto [Index, Element] : enumerate(Elements))
SortedKeys.push_back({getOptimizedPackingGroup(Element, ShaderStage, IOTy),
Element.InterpMode, Element.Rows, Element.Cols,
Element.SigId, static_cast<unsigned>(Index)});
llvm::sort(SortedKeys, [](const SortKey &Left, const SortKey &Right) {
if (Left.Group != Right.Group)
return Left.Group < Right.Group;
if (Left.InterpMode != Right.InterpMode)
return Left.InterpMode < Right.InterpMode;
if (Left.Rows != Right.Rows)
return Left.Rows > Right.Rows;
if (Left.Cols != Right.Cols)
return Left.Cols > Right.Cols;
return Left.SigId < Right.SigId;
});
const unsigned StreamCount =
ShaderStage == Triple::Geometry && IOTy == IOType::Out
? MaxGeometryStreams
: 1;
SmallVector<SignatureRows, 1> Rows(StreamCount);
auto ClipBegin = llvm::partition_point(SortedKeys, [](const SortKey &Key) {
return Key.Group < PackingGroup::ClipCull;
});
auto ClipEnd = llvm::partition_point(
make_range(ClipBegin, SortedKeys.end()),
[](const SortKey &Key) { return Key.Group == PackingGroup::ClipCull; });
auto Pack = [&](auto Begin, auto End) {
auto Order = map_range(make_range(Begin, End), [](const SortKey &Key) {
return Key.OriginalIndex;
});
return packSignatureInOrder(Elements, Order, ShaderStage, IOTy,
UseNative16BitTypes, Rows);
};
Expected<unsigned> Before = Pack(SortedKeys.begin(), ClipBegin);
if (!Before)
return Before.takeError();
SmallVector<unsigned> ClipCullOrder;
for (const SortKey &Key : make_range(ClipBegin, ClipEnd))
ClipCullOrder.push_back(Key.OriginalIndex);
Expected<unsigned> ClipCull =
packOptimizedClipCull(Elements, ClipCullOrder, Rows, UseNative16BitTypes);
if (!ClipCull)
return ClipCull.takeError();
Expected<unsigned> After = Pack(ClipEnd, SortedKeys.end());
if (!After)
return After.takeError();
return std::max({*Before, *ClipCull, *After});
}