| 1 | /* -*- tab-width: 4; -*- */ |
| 2 | /* vi: set sw=2 ts=4 expandtab: */ |
| 3 | |
| 4 | /* Copyright 2019-2020 The Khronos Group Inc. |
| 5 | * SPDX-License-Identifier: Apache-2.0 |
| 6 | */ |
| 7 | |
| 8 | /** |
| 9 | * @file |
| 10 | * @~English |
| 11 | * @brief Utility for interpreting a data format descriptor. |
| 12 | * @author Andrew Garrard |
| 13 | */ |
| 14 | |
| 15 | #include <stdint.h> |
| 16 | #include <stdio.h> |
| 17 | #include <KHR/khr_df.h> |
| 18 | #include "dfd.h" |
| 19 | |
| 20 | /** |
| 21 | * @~English |
| 22 | * @brief Interpret a Data Format Descriptor for a simple format. |
| 23 | * |
| 24 | * @param DFD Pointer to a Data Format Descriptor to interpret, |
| 25 | described as 32-bit words in native endianness. |
| 26 | Note that this is the whole descriptor, not just |
| 27 | the basic descriptor block. |
| 28 | * @param R Information about the decoded red channel, if any. |
| 29 | * @param G Information about the decoded green channel, if any. |
| 30 | * @param B Information about the decoded blue channel, if any. |
| 31 | * @param A Information about the decoded alpha channel, if any. |
| 32 | * @param wordBytes Byte size of the channels (unpacked) or total size (packed). |
| 33 | * |
| 34 | * @return An enumerant describing the decoded value, |
| 35 | * or an error code in case of failure. |
| 36 | **/ |
| 37 | enum InterpretDFDResult interpretDFD(const uint32_t *DFD, |
| 38 | InterpretedDFDChannel *R, |
| 39 | InterpretedDFDChannel *G, |
| 40 | InterpretedDFDChannel *B, |
| 41 | InterpretedDFDChannel *A, |
| 42 | uint32_t *wordBytes) |
| 43 | { |
| 44 | /* We specifically handle "simple" cases that can be translated */ |
| 45 | /* to things a GPU can access. For simplicity, we also ignore */ |
| 46 | /* the compressed formats, which are generally a single sample */ |
| 47 | /* (and I believe are all defined to be little-endian in their */ |
| 48 | /* in-memory layout, even if some documentation confuses this). */ |
| 49 | /* We also just worry about layout and ignore sRGB, since that's */ |
| 50 | /* trivial to extract anyway. */ |
| 51 | |
| 52 | /* DFD points to the whole descriptor, not the basic descriptor block. */ |
| 53 | /* Make everything else relative to the basic descriptor block. */ |
| 54 | const uint32_t *BDFDB = DFD+1; |
| 55 | |
| 56 | uint32_t numSamples = KHR_DFDSAMPLECOUNT(BDFDB); |
| 57 | |
| 58 | uint32_t sampleCounter; |
| 59 | int determinedEndianness = 0; |
| 60 | int determinedNormalizedness = 0; |
| 61 | int determinedSignedness = 0; |
| 62 | int determinedFloatness = 0; |
| 63 | enum InterpretDFDResult result = 0; /* Build this up incrementally. */ |
| 64 | |
| 65 | /* Clear these so following code doesn't get confused. */ |
| 66 | R->offset = R->size = 0; |
| 67 | G->offset = G->size = 0; |
| 68 | B->offset = B->size = 0; |
| 69 | A->offset = A->size = 0; |
| 70 | |
| 71 | /* First rule out the multiple planes case (trivially) */ |
| 72 | /* - that is, we check that only bytesPlane0 is non-zero. */ |
| 73 | /* This means we don't handle YUV even if the API could. */ |
| 74 | /* (We rely on KHR_DF_WORD_BYTESPLANE0..3 being the same and */ |
| 75 | /* KHR_DF_WORD_BYTESPLANE4..7 being the same as a short cut.) */ |
| 76 | if ((BDFDB[KHR_DF_WORD_BYTESPLANE0] & ~KHR_DF_MASK_BYTESPLANE0) |
| 77 | || BDFDB[KHR_DF_WORD_BYTESPLANE4]) return i_UNSUPPORTED_MULTIPLE_PLANES; |
| 78 | |
| 79 | /* Only support the RGB color model. */ |
| 80 | /* We could expand this to allow "UNSPECIFIED" as well. */ |
| 81 | if (KHR_DFDVAL(BDFDB, MODEL) != KHR_DF_MODEL_RGBSDA) return i_UNSUPPORTED_CHANNEL_TYPES; |
| 82 | |
| 83 | /* We only pay attention to sRGB. */ |
| 84 | if (KHR_DFDVAL(BDFDB, TRANSFER) == KHR_DF_TRANSFER_SRGB) result |= i_SRGB_FORMAT_BIT; |
| 85 | |
| 86 | /* We only support samples at coordinate 0,0,0,0. */ |
| 87 | /* (We could confirm this from texel_block_dimensions in 1.2, but */ |
| 88 | /* the interpretation might change in later versions.) */ |
| 89 | for (sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { |
| 90 | if (KHR_DFDSVAL(BDFDB, sampleCounter, SAMPLEPOSITION_ALL)) |
| 91 | return i_UNSUPPORTED_MULTIPLE_SAMPLE_LOCATIONS; |
| 92 | } |
| 93 | |
| 94 | /* Set flags and check for consistency. */ |
| 95 | for (sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { |
| 96 | /* Note: We're ignoring 9995, which is weird and worth special-casing */ |
| 97 | /* rather than trying to generalise to all float formats. */ |
| 98 | if (!determinedFloatness) { |
| 99 | if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) |
| 100 | & KHR_DF_SAMPLE_DATATYPE_FLOAT) { |
| 101 | result |= i_FLOAT_FORMAT_BIT; |
| 102 | determinedFloatness = 1; |
| 103 | } |
| 104 | } else { |
| 105 | /* Check whether we disagree with our predetermined floatness. */ |
| 106 | /* Note that this could justifiably happen with (say) D24S8. */ |
| 107 | if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) |
| 108 | & KHR_DF_SAMPLE_DATATYPE_FLOAT) { |
| 109 | if (!(result & i_FLOAT_FORMAT_BIT)) return i_UNSUPPORTED_MIXED_CHANNELS; |
| 110 | } else { |
| 111 | if ((result & i_FLOAT_FORMAT_BIT)) return i_UNSUPPORTED_MIXED_CHANNELS; |
| 112 | } |
| 113 | } |
| 114 | if (!determinedSignedness) { |
| 115 | if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) |
| 116 | & KHR_DF_SAMPLE_DATATYPE_SIGNED) { |
| 117 | result |= i_SIGNED_FORMAT_BIT; |
| 118 | determinedSignedness = 1; |
| 119 | } |
| 120 | } else { |
| 121 | /* Check whether we disagree with our predetermined signedness. */ |
| 122 | if (KHR_DFDSVAL(BDFDB, sampleCounter, QUALIFIERS) |
| 123 | & KHR_DF_SAMPLE_DATATYPE_SIGNED) { |
| 124 | if (!(result & i_SIGNED_FORMAT_BIT)) return i_UNSUPPORTED_MIXED_CHANNELS; |
| 125 | } else { |
| 126 | if ((result & i_SIGNED_FORMAT_BIT)) return i_UNSUPPORTED_MIXED_CHANNELS; |
| 127 | } |
| 128 | } |
| 129 | /* We define "unnormalized" as "sample_upper = 1". */ |
| 130 | /* We don't check whether any non-1 normalization value is correct */ |
| 131 | /* (i.e. set to the maximum bit value, and check min value) on */ |
| 132 | /* the assumption that we're looking at a format which *came* from */ |
| 133 | /* an API we can support. */ |
| 134 | if (!determinedNormalizedness) { |
| 135 | /* The ambiguity here is if the bottom bit is a single-bit value, */ |
| 136 | /* as in RGBA 5:5:5:1, so we defer the decision if the channel only has one bit. */ |
| 137 | if (KHR_DFDSVAL(BDFDB, sampleCounter, BITLENGTH) > 0) { |
| 138 | if ((result & i_FLOAT_FORMAT_BIT)) { |
| 139 | if (*(float *)(void *)&BDFDB[KHR_DF_WORD_SAMPLESTART + |
| 140 | KHR_DF_WORD_SAMPLEWORDS * sampleCounter + |
| 141 | KHR_DF_SAMPLEWORD_SAMPLEUPPER] != 1.0f) { |
| 142 | result |= i_NORMALIZED_FORMAT_BIT; |
| 143 | } |
| 144 | } else { |
| 145 | if (KHR_DFDSVAL(BDFDB, sampleCounter, SAMPLEUPPER) != 1U) { |
| 146 | result |= i_NORMALIZED_FORMAT_BIT; |
| 147 | } |
| 148 | } |
| 149 | determinedNormalizedness = 1; |
| 150 | } |
| 151 | } |
| 152 | /* Note: We don't check for inconsistent normalization, because */ |
| 153 | /* channels composed of multiple samples will have 0 in the */ |
| 154 | /* lower/upper range. */ |
| 155 | /* This heuristic should handle 64-bit integers, too. */ |
| 156 | } |
| 157 | |
| 158 | /* If this is a packed format, we work out our offsets differently. */ |
| 159 | /* We assume a packed format has channels that aren't byte-aligned. */ |
| 160 | /* If we have a format in which every channel is byte-aligned *and* packed, */ |
| 161 | /* we have the RGBA/ABGR ambiguity; we *probably* don't want the packed */ |
| 162 | /* version in this case, and if hardware has to pack it and swizzle, */ |
| 163 | /* that's up to the hardware to special-case. */ |
| 164 | for (sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { |
| 165 | if (KHR_DFDSVAL(BDFDB, sampleCounter, BITOFFSET) & 0x7U) { |
| 166 | result |= i_PACKED_FORMAT_BIT; |
| 167 | /* Once we're packed, we're packed, no need to keep checking. */ |
| 168 | break; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | /* Remember: the canonical ordering of samples is to start with */ |
| 173 | /* the lowest bit of the channel/location which touches bit 0 of */ |
| 174 | /* the data, when the latter is concatenated in little-endian order, */ |
| 175 | /* and then progress until all the bits of that channel/location */ |
| 176 | /* have been processed. Multiple channels sharing the same source */ |
| 177 | /* bits are processed in channel ID order. (I should clarify this */ |
| 178 | /* for partially-shared data, but it doesn't really matter so long */ |
| 179 | /* as everything is consecutive, except to make things canonical.) */ |
| 180 | /* Note: For standard formats we could determine big/little-endianness */ |
| 181 | /* simply from whether the first sample starts in bit 0; technically */ |
| 182 | /* it's possible to have a format with unaligned channels wherein the */ |
| 183 | /* first channel starts at bit 0 and is one byte, yet other channels */ |
| 184 | /* take more bytes or aren't aligned (e.g. D24S8), but this should be */ |
| 185 | /* irrelevant for the formats that we support. */ |
| 186 | if ((result & i_PACKED_FORMAT_BIT)) { |
| 187 | /* A packed format. */ |
| 188 | uint32_t currentChannel = ~0U; /* Don't start matched. */ |
| 189 | uint32_t currentBitOffset = 0; |
| 190 | uint32_t currentByteOffset = 0; |
| 191 | uint32_t currentBitLength = 0; |
| 192 | *wordBytes = (BDFDB[KHR_DF_WORD_BYTESPLANE0] & 0xFFU); |
| 193 | for (sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { |
| 194 | uint32_t sampleBitOffset = KHR_DFDSVAL(BDFDB, sampleCounter, BITOFFSET); |
| 195 | uint32_t sampleByteOffset = sampleBitOffset >> 3U; |
| 196 | /* The sample bitLength field stores the bit length - 1. */ |
| 197 | uint32_t sampleBitLength = KHR_DFDSVAL(BDFDB, sampleCounter, BITLENGTH) + 1; |
| 198 | uint32_t sampleChannel = KHR_DFDSVAL(BDFDB, sampleCounter, CHANNELID); |
| 199 | InterpretedDFDChannel *sampleChannelPtr; |
| 200 | switch (sampleChannel) { |
| 201 | case KHR_DF_CHANNEL_RGBSDA_RED: |
| 202 | sampleChannelPtr = R; |
| 203 | break; |
| 204 | case KHR_DF_CHANNEL_RGBSDA_GREEN: |
| 205 | sampleChannelPtr = G; |
| 206 | break; |
| 207 | case KHR_DF_CHANNEL_RGBSDA_BLUE: |
| 208 | sampleChannelPtr = B; |
| 209 | break; |
| 210 | case KHR_DF_CHANNEL_RGBSDA_ALPHA: |
| 211 | sampleChannelPtr = A; |
| 212 | break; |
| 213 | default: |
| 214 | return i_UNSUPPORTED_CHANNEL_TYPES; |
| 215 | } |
| 216 | if (sampleChannel == currentChannel) { |
| 217 | /* Continuation of the same channel. */ |
| 218 | /* Since a big (>32-bit) channel isn't "packed", */ |
| 219 | /* this should only happen in big-endian, or if */ |
| 220 | /* we have a wacky format that we won't support. */ |
| 221 | if (sampleByteOffset == currentByteOffset - 1U && /* One byte earlier */ |
| 222 | ((currentBitOffset + currentBitLength) & 7U) == 0 && /* Already at the end of a byte */ |
| 223 | (sampleBitOffset & 7U) == 0) { /* Start at the beginning of the byte */ |
| 224 | /* All is good, continue big-endian. */ |
| 225 | /* N.B. We shouldn't be here if we decided we were little-endian, */ |
| 226 | /* so we don't bother to check that disagreement. */ |
| 227 | result |= i_BIG_ENDIAN_FORMAT_BIT; |
| 228 | determinedEndianness = 1; |
| 229 | } else { |
| 230 | /* Oh dear. */ |
| 231 | /* We could be little-endian, but not with any standard format. */ |
| 232 | /* More likely we've got something weird that we can't support. */ |
| 233 | return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; |
| 234 | } |
| 235 | /* Remember where we are. */ |
| 236 | currentBitOffset = sampleBitOffset; |
| 237 | currentByteOffset = sampleByteOffset; |
| 238 | currentBitLength = sampleBitLength; |
| 239 | /* Accumulate the bit length. */ |
| 240 | sampleChannelPtr->size += sampleBitLength; |
| 241 | } else { |
| 242 | /* Everything is new. Hopefully. */ |
| 243 | currentChannel = sampleChannel; |
| 244 | currentBitOffset = sampleBitOffset; |
| 245 | currentByteOffset = sampleByteOffset; |
| 246 | currentBitLength = sampleBitLength; |
| 247 | if (sampleChannelPtr->size) { |
| 248 | /* Uh-oh, we've seen this channel before. */ |
| 249 | return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; |
| 250 | } |
| 251 | /* For now, record the bit offset in little-endian terms, */ |
| 252 | /* because we may not know to reverse it yet. */ |
| 253 | sampleChannelPtr->offset = sampleBitOffset; |
| 254 | sampleChannelPtr->size = sampleBitLength; |
| 255 | } |
| 256 | } |
| 257 | if ((result & i_BIG_ENDIAN_FORMAT_BIT)) { |
| 258 | /* Our bit offsets to bit 0 of each channel are in little-endian terms. */ |
| 259 | /* We need to do a byte swap to work out where they should be. */ |
| 260 | /* We assume, for sanity, that byte sizes are a power of two for this. */ |
| 261 | uint32_t offsetMask = (*wordBytes - 1U) << 3U; |
| 262 | R->offset ^= offsetMask; |
| 263 | G->offset ^= offsetMask; |
| 264 | B->offset ^= offsetMask; |
| 265 | A->offset ^= offsetMask; |
| 266 | } |
| 267 | } else { |
| 268 | /* Not a packed format. */ |
| 269 | /* Everything is byte-aligned. */ |
| 270 | /* Question is whether there multiple samples per channel. */ |
| 271 | uint32_t currentChannel = ~0U; /* Don't start matched. */ |
| 272 | uint32_t currentByteOffset = 0; |
| 273 | uint32_t currentByteLength = 0; |
| 274 | for (sampleCounter = 0; sampleCounter < numSamples; ++sampleCounter) { |
| 275 | uint32_t sampleByteOffset = KHR_DFDSVAL(BDFDB, sampleCounter, BITOFFSET) >> 3U; |
| 276 | uint32_t sampleByteLength = (KHR_DFDSVAL(BDFDB, sampleCounter, BITLENGTH) + 1) >> 3U; |
| 277 | uint32_t sampleChannel = KHR_DFDSVAL(BDFDB, sampleCounter, CHANNELID); |
| 278 | InterpretedDFDChannel *sampleChannelPtr; |
| 279 | switch (sampleChannel) { |
| 280 | case KHR_DF_CHANNEL_RGBSDA_RED: |
| 281 | sampleChannelPtr = R; |
| 282 | break; |
| 283 | case KHR_DF_CHANNEL_RGBSDA_GREEN: |
| 284 | sampleChannelPtr = G; |
| 285 | break; |
| 286 | case KHR_DF_CHANNEL_RGBSDA_BLUE: |
| 287 | sampleChannelPtr = B; |
| 288 | break; |
| 289 | case KHR_DF_CHANNEL_RGBSDA_ALPHA: |
| 290 | sampleChannelPtr = A; |
| 291 | break; |
| 292 | default: |
| 293 | return i_UNSUPPORTED_CHANNEL_TYPES; |
| 294 | } |
| 295 | if (sampleChannel == currentChannel) { |
| 296 | /* Continuation of the same channel. */ |
| 297 | /* Either big-endian, or little-endian with a very large channel. */ |
| 298 | if (sampleByteOffset == currentByteOffset - 1) { /* One byte earlier */ |
| 299 | if (determinedEndianness && !(result & i_BIG_ENDIAN_FORMAT_BIT)) { |
| 300 | return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; |
| 301 | } |
| 302 | /* All is good, continue big-endian. */ |
| 303 | result |= i_BIG_ENDIAN_FORMAT_BIT; |
| 304 | determinedEndianness = 1; |
| 305 | /* Update the start */ |
| 306 | sampleChannelPtr->offset = sampleByteOffset; |
| 307 | } else if (sampleByteOffset == currentByteOffset + currentByteLength) { |
| 308 | if (determinedEndianness && (result & i_BIG_ENDIAN_FORMAT_BIT)) { |
| 309 | return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; |
| 310 | } |
| 311 | /* All is good, continue little-endian. */ |
| 312 | determinedEndianness = 1; |
| 313 | } else { |
| 314 | /* Oh dear. */ |
| 315 | /* We could be little-endian, but not with any standard format. */ |
| 316 | /* More likely we've got something weird that we can't support. */ |
| 317 | return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; |
| 318 | } |
| 319 | /* Remember where we are. */ |
| 320 | currentByteOffset = sampleByteOffset; |
| 321 | currentByteLength = sampleByteLength; |
| 322 | /* Accumulate the byte length. */ |
| 323 | sampleChannelPtr->size += sampleByteLength; |
| 324 | /* Assume these are all the same. */ |
| 325 | *wordBytes = sampleChannelPtr->size; |
| 326 | } else { |
| 327 | /* Everything is new. Hopefully. */ |
| 328 | currentChannel = sampleChannel; |
| 329 | currentByteOffset = sampleByteOffset; |
| 330 | currentByteLength = sampleByteLength; |
| 331 | if (sampleChannelPtr->size) { |
| 332 | /* Uh-oh, we've seen this channel before. */ |
| 333 | return i_UNSUPPORTED_NONTRIVIAL_ENDIANNESS; |
| 334 | } |
| 335 | /* For now, record the byte offset in little-endian terms, */ |
| 336 | /* because we may not know to reverse it yet. */ |
| 337 | sampleChannelPtr->offset = sampleByteOffset; |
| 338 | sampleChannelPtr->size = sampleByteLength; |
| 339 | /* Assume these are all the same. */ |
| 340 | *wordBytes = sampleByteLength; |
| 341 | } |
| 342 | } |
| 343 | } |
| 344 | return result; |
| 345 | } |
| 346 | |