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 **/
37enum 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