blob: 7395b42ec4f28342cbd252331b5430cc6950fd2b [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/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;
};
// Everything the packing rules need to know about the element that is being
// placed. It applies to every row that the element covers.
struct ElementPlacement {
unsigned Rows;
unsigned Cols;
unsigned ComponentWidth;
dxbc::PSV::InterpolationMode InterpMode;
SemanticInterpretation Interpretation;
};
// 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;
};
} // namespace
static uint8_t getStartColumn(uint8_t ColumnMask) {
assert(ColumnMask != 0 && "expected at least one occupied column");
return countr_zero(ColumnMask);
}
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;
}
}
// 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 &&
Placement.Interpretation < 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,
SemanticSignatureElement &Element) {
placeRowsAt(Rows, StartRow, Placement, ColumnMask);
Element.StartRow = StartRow;
Element.StartCol = getStartColumn(ColumnMask);
}
static bool prefixPackElement(SemanticSignatureElement &Element,
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, Element);
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(SemanticSignatureElement &Element,
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);
Element.StartRow = State.SignatureRows[ClipCullStartRow];
Element.StartCol = getStartColumn(*ColumnMask);
return std::nullopt;
}
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;
}
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");
// Only a geometry shader output signature packs its streams independently.
const unsigned StreamCount =
ShaderStage == Triple::EnvironmentType::Geometry && IOTy == IOType::Out
? MaxGeometryStreams
: 1;
SmallVector<std::array<SignatureRow, MaxSignatureRows>, 1> Rows(StreamCount);
SmallVector<ClipCullState, 1> ClipCullStates(StreamCount);
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");
if (Element.GSStream >= StreamCount)
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 unsigned ComponentWidth =
getComponentWidth(Element.CompType, UseNative16BitTypes);
// Only a tess factor that covers multiple rows is dynamically indexable
// and needs to be reserved in the last column.
const SemanticInterpretation PackingInterpretation =
Interpretation == SemanticInterpretation::TessFactor &&
Element.Rows == 1
? SemanticInterpretation::SV
: Interpretation;
const ElementPlacement Placement = {Element.Rows, Element.Cols,
ComponentWidth, Element.InterpMode,
PackingInterpretation};
const unsigned StreamIndex = Element.GSStream;
MutableArrayRef<SignatureRow> StreamRows = Rows[StreamIndex];
if (Interpretation == SemanticInterpretation::ClipCull) {
if (std::optional<SignaturePackingError::ErrorKind> Kind =
packClipCullElement(Element, StreamRows,
ClipCullStates[StreamIndex], Placement))
return make_error<SignaturePackingError>(*Kind,
static_cast<unsigned>(Index));
} else if (!prefixPackElement(Element, StreamRows, Placement)) {
return make_error<SignaturePackingError>(
SignaturePackingError::SignatureOverflow,
static_cast<unsigned>(Index));
}
NumRows = std::max(NumRows, Element.StartRow + Element.Rows);
}
return NumRows;
}
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;
}