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 | |