/* -*- tab-width: 4; -*- */ /* vi: set sw=2 ts=4 expandtab: */ /* Copyright 2019-2020 The Khronos Group Inc. * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @~English * @brief Utility for interpreting a data format descriptor. * @author Andrew Garrard */ #include #include #include #include "dfd.h" static uint32_t bit_ceil(uint32_t x) { x -= 1; for (uint32_t i = 0; i < sizeof(x) * 8; ++i) if (1u << i > x) return 1u << i; return 0; } /** * @~English * @brief Interpret a Data Format Descriptor for a simple format. * * Handles "simple" cases that can be translated to things a GPU can access. * For simplicity, it ignores the compressed formats, which are generally a * single sample (and I believe are all defined to be little-endian in their * in-memory layout, even if some documentation confuses this). Focuses on * the layout and ignores sRGB except for reporting if that is the transfer * function by way of a bit in the returned value. * * @param[in] DFD Pointer to a Data Format Descriptor to interpret, * described as 32-bit words in native endianness. * Note that this is the whole descriptor, not just * the basic descriptor block. * @param R[in,out] Pointer to struct to receive information about the decoded * red channel, the Y channel, if YUV, or the depth channel, * if any. * @param G[in,out] Pointer to struct to receive information about the decoded * green channel, the U (Cb) channel, if YUV, or the stencil * channel, if any. * @param B[in,out] Pointer to struct to receive information about the decoded * blue channel, if any or the V (Cr) channel, if YUV. * @param A[in,out] Pointer to struct to receive information about the decoded * alpha channel, if any or the second Y channel, if YUV and * any. * @param wordBytes[in,out] Pointer to a uint32_t to receive the byte size of * the channels (unpacked) or total size (packed). * * @return An enumerant describing the decoded value, * or an error code in case of failure. * * The mapping of YUV channels to the parameter names used here is based on * the channel ids in @c khr_df.h and is different from the convention used * in format names in the Vulkan specification where G == Y, R = Cr and B = Cb. **/ enum InterpretDFDResult interpretDFD(const uint32_t *DFD, InterpretedDFDChannel *R, InterpretedDFDChannel *G, InterpretedDFDChannel *B, InterpretedDFDChannel *A, uint32_t *wordBytes) { /* DFD points to the whole descriptor, not the basic descriptor block. */ /* Make everything else relative to the basic descriptor block. */ const uint32_t *BDFDB = DFD+1; uint32_t numSamples = KHR_DFDSAMPLECOUNT(BDFDB); if (numSamples == 0) return i_UNSUPPORTED_CHANNEL_TYPES; int determinedEndianness = 0; enum InterpretDFDResult result = 0; /* Build this up incrementally. */ bool isDepthStencil = false; /* Clear these so following code doesn't get confused. */ R->offset = R->size = 0; G->offset = G->size = 0; B->offset = B->size = 0; A->offset = A->size = 0; /* First rule out the multiple planes case (trivially) */ /* - that is, we check that only bytesPlane0 is non-zero. */ /* This means we don't handle multi-plane YUV, even if the API could. */ /* (We rely on KHR_DF_WORD_BYTESPLANE0..3 being the same and */ /* KHR_DF_WORD_BYTESPLANE4..7 being the same as a short cut.) */ if ((BDFDB[KHR_DF_WORD_BYTESPLANE0] & ~KHR_DF_MASK_BYTESPLANE0) || BDFDB[KHR_DF_WORD_BYTESPLANE4]) return i_UNSUPPORTED_MULTIPLE_PLANES; /* If this is a packed format, we work out our offsets differently. */ /* We assume a packed format has channels that aren't byte-aligned. */ /* If we have a format in which every channel is byte-aligned *and* packed, */ /* we have the RGBA/ABGR ambiguity; we *probably* don't want the packed */ /* version in this case, and if hardware has to pack it and swizzle, */ /* that's up to the hardware to special-case. */ for (uint32_t sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { uint32_t offset = KHR_DFDSVAL(BDFDB, sampleCounter, BITOFFSET); uint32_t length = KHR_DFDSVAL(BDFDB, sampleCounter, BITLENGTH) + 1; if ((offset & 0x7U) || ((offset + length) & 0x7U)) { result |= i_PACKED_FORMAT_BIT; /* Once we're packed, we're packed, no need to keep checking. */ break; } } // Check data types. bool hasSigned = false; bool hasFloat = false; bool hasNormalized = false; bool hasFixed = false; khr_df_model_e model = KHR_DFDVAL(BDFDB, MODEL); // Note: We're ignoring 9995, which is weird and worth special-casing // rather than trying to generalise to all float formats. for (uint32_t i = 0; i < numSamples; ++i) { const bool isSigned = (KHR_DFDSVAL(BDFDB, i, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0; const bool isFloat = (KHR_DFDSVAL(BDFDB, i, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0; // We define "unnormalized" as "sample_upper = 1" or "sample_upper = 1.0f". // We don't check whether any non-1 normalization value is correct // (i.e. set to the maximum bit value, and check min value) on // the assumption that we're looking at a format which *came* from // an API we can support. bool isFixed; bool isNormalized; if (isFloat) { isNormalized = *(float*) (void*) &BDFDB[KHR_DF_WORD_SAMPLESTART + KHR_DF_WORD_SAMPLEWORDS * i + KHR_DF_SAMPLEWORD_SAMPLEUPPER] != 1.0f; isFixed = false; } else { uint32_t sampleUpper = KHR_DFDSVAL(BDFDB, i, SAMPLEUPPER); uint32_t maxVal = 1U << KHR_DFDSVAL(BDFDB, i, BITLENGTH); if (!isSigned) maxVal <<= 1; maxVal--; isFixed = 1U < sampleUpper && sampleUpper < maxVal; isNormalized = !isFixed && sampleUpper != 1U; } hasSigned |= isSigned; hasFixed |= isFixed; hasFloat |= isFloat; // By our definition the normalizedness of a single bit channel (like in RGBA 5:5:5:1) // is ambiguous. Ignore these during normalized checks. if (KHR_DFDSVAL(BDFDB, i, BITLENGTH) > 0) hasNormalized |= isNormalized; } result |= hasSigned ? i_SIGNED_FORMAT_BIT : 0; result |= hasFloat ? i_FLOAT_FORMAT_BIT : 0; result |= hasNormalized ? i_NORMALIZED_FORMAT_BIT : 0; result |= hasFixed ? i_FIXED_FORMAT_BIT : 0; // Checks based on color model if (model == KHR_DF_MODEL_YUVSDA) { result |= i_NORMALIZED_FORMAT_BIT; result |= i_COMPRESSED_FORMAT_BIT; result |= i_YUVSDA_FORMAT_BIT; for (uint32_t i = 0; i < numSamples; ++i) { switch (KHR_DFDSVAL(BDFDB, i, CHANNELID)) { case KHR_DF_CHANNEL_YUVSDA_Y: case KHR_DF_CHANNEL_YUVSDA_U: case KHR_DF_CHANNEL_YUVSDA_V: case KHR_DF_CHANNEL_YUVSDA_A: break; case KHR_DF_CHANNEL_YUVSDA_DEPTH: case KHR_DF_CHANNEL_YUVSDA_STENCIL: isDepthStencil = true; break; default: return i_UNSUPPORTED_CHANNEL_TYPES; } } // Determine wordBytes uint32_t largestSampleSize = 0; for (uint32_t i = 0; i < numSamples; ++i) { uint32_t length = KHR_DFDSVAL(BDFDB, i, BITLENGTH) + 1; if (largestSampleSize < length) largestSampleSize = length; } *wordBytes = ((result & i_PACKED_FORMAT_BIT) ? 4 : 1) * bit_ceil(largestSampleSize) / 8; } else if (KHR_DFDVAL(BDFDB, MODEL) == KHR_DF_MODEL_RGBSDA) { /* Check if transfer is sRGB. */ if (KHR_DFDVAL(BDFDB, TRANSFER) == KHR_DF_TRANSFER_SRGB) result |= i_SRGB_FORMAT_BIT; /* We only support samples at coordinate 0,0,0,0. */ /* (We could confirm this from texel_block_dimensions in 1.2, but */ /* the interpretation might change in later versions.) */ for (uint32_t sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { if (KHR_DFDSVAL(BDFDB, sampleCounter, SAMPLEPOSITION_ALL)) return i_UNSUPPORTED_MULTIPLE_SAMPLE_LOCATIONS; } } if (model == KHR_DF_MODEL_RGBSDA || model == KHR_DF_MODEL_YUVSDA) { /* The values of the DEPTH and STENCIL tokens are the same for */ /* RGBSDA and YUVSDA. */ /* For Depth/Stencil formats mixed channels are allowed */ for (uint32_t sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { switch (KHR_DFDSVAL(BDFDB, sampleCounter, CHANNELID)) { case KHR_DF_CHANNEL_RGBSDA_DEPTH: case KHR_DF_CHANNEL_RGBSDA_STENCIL: isDepthStencil = true; break; default: break; } } // Check for mixed channels if (!isDepthStencil) { for (uint32_t i = 0; i < numSamples; ++i) { const bool isSigned = (KHR_DFDSVAL(BDFDB, i, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0; const bool isFloat = (KHR_DFDSVAL(BDFDB, i, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0; if (isSigned != hasSigned) return i_UNSUPPORTED_MIXED_CHANNELS; if (isFloat != hasFloat) return i_UNSUPPORTED_MIXED_CHANNELS; // Note: We don't check for inconsistent normalization, because // channels composed of multiple samples will have 0 in the // lower/upper range. Single bit channels are also ambiguous. // This heuristic should handle 64-bit integers, too. } } /* This all relies on the channel id values for RGB being equal to */ /* those for YUV. */ /* Remember: the canonical ordering of samples is to start with */ /* the lowest bit of the channel/location which touches bit 0 of */ /* the data, when the latter is concatenated in little-endian order, */ /* and then progress until all the bits of that channel/location */ /* have been processed. Multiple channels sharing the same source */ /* bits are processed in channel ID order. (I should clarify this */ /* for partially-shared data, but it doesn't really matter so long */ /* as everything is consecutive, except to make things canonical.) */ /* Note: For standard formats we could determine big/little-endianness */ /* simply from whether the first sample starts in bit 0; technically */ /* it's possible to have a format with unaligned channels wherein the */ /* first channel starts at bit 0 and is one byte, yet other channels */ /* take more bytes or aren't aligned (e.g. D24S8), but this should be */ /* irrelevant for the formats that we support. */ if ((result & i_PACKED_FORMAT_BIT)) { /* A packed format. */ uint32_t currentChannel = ~0U; /* Don't start matched. */ uint32_t currentBitOffset = 0; uint32_t currentByteOffset = 0; uint32_t currentBitLength = 0; *wordBytes = (BDFDB[KHR_DF_WORD_BYTESPLANE0] & 0xFFU); for (uint32_t sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { uint32_t sampleBitOffset = KHR_DFDSVAL(BDFDB, sampleCounter, BITOFFSET); uint32_t sampleByteOffset = sampleBitOffset >> 3U; /* The sample bitLength field stores the bit length - 1. */ uint32_t sampleBitLength = KHR_DFDSVAL(BDFDB, sampleCounter, BITLENGTH) + 1; uint32_t sampleChannel = KHR_DFDSVAL(BDFDB, sampleCounter, CHANNELID); InterpretedDFDChannel *sampleChannelPtr; switch (sampleChannel) { case KHR_DF_CHANNEL_RGBSDA_RED: sampleChannelPtr = R; break; case KHR_DF_CHANNEL_RGBSDA_GREEN: sampleChannelPtr = G; break; case KHR_DF_CHANNEL_RGBSDA_BLUE: sampleChannelPtr = B; break; case KHR_DF_CHANNEL_RGBSDA_DEPTH: sampleChannelPtr = R; break; case KHR_DF_CHANNEL_RGBSDA_STENCIL: sampleChannelPtr = G; break; case KHR_DF_CHANNEL_RGBSDA_ALPHA: sampleChannelPtr = A; break; default: return i_UNSUPPORTED_CHANNEL_TYPES; } if (sampleChannel == currentChannel) { /* Continuation of the same channel. */ /* Since a big (>32-bit) channel isn't "packed", */ /* this should only happen in big-endian, or if */ /* we have a wacky format that we won't support. */ if (sampleByteOffset == currentByteOffset - 1U && /* One byte earlier */ ((currentBitOffset + currentBitLength) & 7U) == 0 && /* Already at the end of a byte */ (sampleBitOffset & 7U) == 0) { /* Start at the beginning of the byte */ /* All is good, continue big-endian. */ /* N.B. We shouldn't be here if we decided we were little-endian, */ /* so we don't bother to check that disagreement. */ result |= i_BIG_ENDIAN_FORMAT_BIT; determinedEndianness = 1; } else { /* Oh dear. */ /* We could be little-endian, but not with any standard format. */ /* More likely we've got something weird that we can't support. */ return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; } /* Remember where we are. */ currentBitOffset = sampleBitOffset; currentByteOffset = sampleByteOffset; currentBitLength = sampleBitLength; /* Accumulate the bit length. */ sampleChannelPtr->size += sampleBitLength; } else { /* Everything is new. Hopefully. */ currentChannel = sampleChannel; currentBitOffset = sampleBitOffset; currentByteOffset = sampleByteOffset; currentBitLength = sampleBitLength; if (sampleChannelPtr->size) { if (model == KHR_DF_MODEL_YUVSDA && sampleChannel == KHR_DF_CHANNEL_YUVSDA_Y) { if (sampleChannelPtr == R) { /* We've got another Y channel. Record details in A. */ if (A->size == 0) { sampleChannelPtr = A; } else { /* Uh-oh, we've already got a second Y or an alpha channel. */ return i_UNSUPPORTED_CHANNEL_TYPES; } } } else { /* Uh-oh, we've seen this channel before. */ return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; } } /* For now, record the bit offset in little-endian terms, */ /* because we may not know to reverse it yet. */ sampleChannelPtr->offset = sampleBitOffset; sampleChannelPtr->size = sampleBitLength; } } if ((result & i_BIG_ENDIAN_FORMAT_BIT)) { /* Our bit offsets to bit 0 of each channel are in little-endian terms. */ /* We need to do a byte swap to work out where they should be. */ /* We assume, for sanity, that byte sizes are a power of two for this. */ uint32_t offsetMask = (*wordBytes - 1U) << 3U; R->offset ^= offsetMask; G->offset ^= offsetMask; B->offset ^= offsetMask; A->offset ^= offsetMask; } } else { /* Not a packed format. */ /* Everything is byte-aligned. */ /* Question is whether there multiple samples per channel. */ uint32_t currentChannel = ~0U; /* Don't start matched. */ uint32_t currentByteOffset = 0; uint32_t currentByteLength = 0; for (uint32_t sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { uint32_t sampleByteOffset = KHR_DFDSVAL(BDFDB, sampleCounter, BITOFFSET) >> 3U; uint32_t sampleByteLength = (KHR_DFDSVAL(BDFDB, sampleCounter, BITLENGTH) + 1) >> 3U; uint32_t sampleChannel = KHR_DFDSVAL(BDFDB, sampleCounter, CHANNELID); InterpretedDFDChannel *sampleChannelPtr; switch (sampleChannel) { case KHR_DF_CHANNEL_RGBSDA_RED: sampleChannelPtr = R; break; case KHR_DF_CHANNEL_RGBSDA_GREEN: sampleChannelPtr = G; break; case KHR_DF_CHANNEL_RGBSDA_BLUE: sampleChannelPtr = B; break; case KHR_DF_CHANNEL_RGBSDA_DEPTH: sampleChannelPtr = R; break; case KHR_DF_CHANNEL_RGBSDA_STENCIL: sampleChannelPtr = G; break; case KHR_DF_CHANNEL_RGBSDA_ALPHA: sampleChannelPtr = A; break; default: return i_UNSUPPORTED_CHANNEL_TYPES; } if (sampleChannel == currentChannel) { /* Continuation of the same channel. */ /* Either big-endian, or little-endian with a very large channel. */ if (sampleByteOffset == currentByteOffset - 1) { /* One byte earlier */ if (determinedEndianness && !(result & i_BIG_ENDIAN_FORMAT_BIT)) { return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; } /* All is good, continue big-endian. */ result |= i_BIG_ENDIAN_FORMAT_BIT; determinedEndianness = 1; /* Update the start */ sampleChannelPtr->offset = sampleByteOffset; } else if (sampleByteOffset == currentByteOffset + currentByteLength) { if (determinedEndianness && (result & i_BIG_ENDIAN_FORMAT_BIT)) { return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; } /* All is good, continue little-endian. */ determinedEndianness = 1; } else { /* Oh dear. */ /* We could be little-endian, but not with any standard format. */ /* More likely we've got something weird that we can't support. */ return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; } /* Remember where we are. */ currentByteOffset = sampleByteOffset; currentByteLength = sampleByteLength; /* Accumulate the byte length. */ sampleChannelPtr->size += sampleByteLength; /* Assume these are all the same. */ *wordBytes = sampleChannelPtr->size; } else { /* Everything is new. Hopefully. */ currentChannel = sampleChannel; currentByteOffset = sampleByteOffset; currentByteLength = sampleByteLength; if (sampleChannelPtr->size) { if (model == KHR_DF_MODEL_YUVSDA && sampleChannel == KHR_DF_CHANNEL_YUVSDA_Y) { if (sampleChannelPtr == R) { /* We've got another Y channel. Record details in A. */ if (A->size == 0) { sampleChannelPtr = A; } else { /* Uh-oh, we've already got a second Y or an alpha channel. */ return i_UNSUPPORTED_CHANNEL_TYPES; } } } else { /* Uh-oh, we've seen this channel before. */ return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; } } /* For now, record the byte offset in little-endian terms, */ /* because we may not know to reverse it yet. */ sampleChannelPtr->offset = sampleByteOffset; sampleChannelPtr->size = sampleByteLength; /* Assume these are all the same. */ *wordBytes = sampleByteLength; } } } } else { return i_UNSUPPORTED_CHANNEL_TYPES; } if (isDepthStencil) { /* For Depth/Stencil formats wordBytes is determined by the required alignment of */ /* the larger channel. */ uint32_t largerSize = R->size > G->size ? R->size : G->size; *wordBytes = bit_ceil(largerSize); } return result; }