1 /* Copyright 2022 Advanced Micro Devices, Inc.
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a
4 * copy of this software and associated documentation files (the "Software"),
5 * to deal in the Software without restriction, including without limitation
6 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
7 * and/or sell copies of the Software, and to permit persons to whom the
8 * Software is furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
17 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19 * OTHER DEALINGS IN THE SOFTWARE.
20 *
21 * Authors: AMD
22 *
23 */
24
25 #include "vpe_assert.h"
26 #include <string.h>
27 #include "color.h"
28 #include "color_gamma.h"
29 #include "color_cs.h"
30 #include "vpe_priv.h"
31 #include "color_gamut.h"
32 #include "common.h"
33 #include "custom_float.h"
34 #include "color_test_values.h"
35 #include "color_pwl.h"
36 #include "3dlut_builder.h"
37 #include "shaper_builder.h"
38
39 static void color_check_input_cm_update(struct vpe_priv *vpe_priv, struct stream_ctx *stream_ctx,
40 const struct vpe_color_space *vcs, const struct vpe_color_adjust *adjustments,
41 bool enable_3dlut);
42
43 static void color_check_output_cm_update(
44 struct vpe_priv *vpe_priv, const struct vpe_color_space *vcs);
45
46 static bool color_update_regamma_tf(struct vpe_priv *vpe_priv,
47 enum color_transfer_func output_transfer_function, struct fixed31_32 x_scale,
48 struct fixed31_32 y_scale, struct fixed31_32 y_bias, bool can_bypass,
49 struct transfer_func *output_tf);
50
51 static bool color_update_degamma_tf(struct vpe_priv *vpe_priv,
52 enum color_transfer_func color_input_tf, struct fixed31_32 x_scale,
53 struct fixed31_32 y_scale, struct fixed31_32 y_bias, bool can_bypass,
54 struct transfer_func *input_tf);
55
56 static bool color_update_input_cs(struct vpe_priv *vpe_priv, enum color_space in_cs,
57 const struct vpe_color_adjust *adjustments, struct vpe_csc_matrix *input_cs,
58 struct vpe_color_adjust *stream_clr_adjustments, struct fixed31_32 *matrix_scaling_factor);
59
60 static bool is_ycbcr(enum color_space in_cs);
61
get_shaper_norm_factor(struct vpe_tonemap_params * tm_params,struct stream_ctx * stream_ctx,uint32_t * shaper_norm_factor)62 static void get_shaper_norm_factor(struct vpe_tonemap_params *tm_params,
63 struct stream_ctx *stream_ctx, uint32_t *shaper_norm_factor)
64 {
65 if (tm_params->shaper_tf == VPE_TF_PQ_NORMALIZED) {
66 if (tm_params->input_pq_norm_factor == 0) {
67 *shaper_norm_factor = stream_ctx->stream.hdr_metadata.max_mastering;
68 } else {
69 *shaper_norm_factor = tm_params->input_pq_norm_factor;
70 }
71 } else {
72 *shaper_norm_factor = HDR_PEAK_WHITE;
73 }
74 }
75
is_ycbcr(enum color_space in_cs)76 static bool is_ycbcr(enum color_space in_cs)
77 {
78 if ((in_cs == COLOR_SPACE_YCBCR601) || (in_cs == COLOR_SPACE_YCBCR601_LIMITED) ||
79 (in_cs == COLOR_SPACE_YCBCR709) || (in_cs == COLOR_SPACE_YCBCR709_LIMITED) ||
80 (in_cs == COLOR_SPACE_2020_YCBCR) || (in_cs == COLOR_SPACE_2020_YCBCR_LIMITED)) {
81 return true;
82 }
83 return false;
84 }
85
color_check_output_cm_update(struct vpe_priv * vpe_priv,const struct vpe_color_space * vcs)86 static void color_check_output_cm_update(
87 struct vpe_priv *vpe_priv, const struct vpe_color_space *vcs)
88 {
89 enum color_space cs;
90 enum color_transfer_func tf;
91
92 vpe_color_get_color_space_and_tf(vcs, &cs, &tf);
93
94 if (cs == COLOR_SPACE_UNKNOWN || tf == TRANSFER_FUNC_UNKNOWN)
95 VPE_ASSERT(0);
96
97 if (cs != vpe_priv->output_ctx.cs) {
98 vpe_priv->output_ctx.dirty_bits.color_space = 1;
99 vpe_priv->output_ctx.cs = cs;
100 } else {
101 vpe_priv->output_ctx.dirty_bits.color_space = 0;
102 }
103
104 if (tf != vpe_priv->output_ctx.tf) {
105 vpe_priv->output_ctx.dirty_bits.transfer_function = 1;
106 vpe_priv->output_ctx.tf = tf;
107 } else {
108 vpe_priv->output_ctx.dirty_bits.transfer_function = 0;
109 }
110 }
111
color_check_input_cm_update(struct vpe_priv * vpe_priv,struct stream_ctx * stream_ctx,const struct vpe_color_space * vcs,const struct vpe_color_adjust * adjustments,bool enable_3dlut)112 static void color_check_input_cm_update(struct vpe_priv *vpe_priv, struct stream_ctx *stream_ctx,
113 const struct vpe_color_space *vcs, const struct vpe_color_adjust *adjustments,
114 bool enable_3dlut)
115 {
116 enum color_space cs;
117 enum color_transfer_func tf;
118
119 vpe_color_get_color_space_and_tf(vcs, &cs, &tf);
120 /*
121 * Bias and Scale already does full->limited range conversion.
122 * Hence, the ICSC matrix should always be full range
123 */
124 vpe_convert_full_range_color_enum(&cs);
125
126 if (cs == COLOR_SPACE_UNKNOWN && tf == TRANSFER_FUNC_UNKNOWN)
127 VPE_ASSERT(0);
128
129 if (cs != stream_ctx->cs || enable_3dlut != stream_ctx->enable_3dlut) {
130 stream_ctx->dirty_bits.color_space = 1;
131 stream_ctx->cs = cs;
132 } else {
133 stream_ctx->dirty_bits.color_space = 0;
134 if (adjustments) {
135 if (vpe_color_different_color_adjusts(
136 adjustments, &stream_ctx->color_adjustments)) // the new stream has different
137 // color adjustments params
138 stream_ctx->dirty_bits.color_space = 1;
139 }
140 }
141 // if the new transfer function is different than the old one or the scaling factor is not one
142 // any new stream will start with a transfer function which is not scaled
143 if (tf != stream_ctx->tf || enable_3dlut != stream_ctx->enable_3dlut) {
144 stream_ctx->dirty_bits.transfer_function = 1;
145 stream_ctx->tf = tf;
146 } else {
147 stream_ctx->dirty_bits.transfer_function = 0;
148 }
149
150 stream_ctx->enable_3dlut = enable_3dlut;
151 }
152
color_update_regamma_tf(struct vpe_priv * vpe_priv,enum color_transfer_func output_transfer_function,struct fixed31_32 x_scale,struct fixed31_32 y_scale,struct fixed31_32 y_bias,bool can_bypass,struct transfer_func * output_tf)153 static bool color_update_regamma_tf(struct vpe_priv *vpe_priv,
154 enum color_transfer_func output_transfer_function, struct fixed31_32 x_scale,
155 struct fixed31_32 y_scale, struct fixed31_32 y_bias, bool can_bypass,
156 struct transfer_func* output_tf)
157 {
158 struct pwl_params *params = NULL;
159
160 if (can_bypass) {
161 output_tf->type = TF_TYPE_BYPASS;
162 return true;
163 }
164
165 output_tf->sdr_ref_white_level = 80;
166 output_tf->cm_gamma_type = CM_REGAM;
167 output_tf->type = TF_TYPE_DISTRIBUTED_POINTS;
168 output_tf->start_base = y_bias;
169
170 switch (output_transfer_function) {
171 case TRANSFER_FUNC_SRGB:
172 case TRANSFER_FUNC_BT709:
173 case TRANSFER_FUNC_BT1886:
174 case TRANSFER_FUNC_PQ2084:
175 case TRANSFER_FUNC_LINEAR_0_125:
176 case TRANSFER_FUNC_LINEAR_0_1:
177 output_tf->tf = output_transfer_function;
178 break;
179 default:
180 VPE_ASSERT(0);
181 break;
182 }
183
184 if (!vpe_priv->init.debug.force_tf_calculation &&
185 x_scale.value == vpe_fixpt_one.value &&
186 y_scale.value == vpe_fixpt_one.value &&
187 y_bias.value == vpe_fixpt_zero.value)
188 vpe_priv->resource.get_tf_pwl_params(output_tf, ¶ms, CM_REGAM);
189
190 if (params)
191 output_tf->use_pre_calculated_table = true;
192 else
193 output_tf->use_pre_calculated_table = false;
194
195 if (!output_tf->use_pre_calculated_table)
196 vpe_color_calculate_regamma_params(vpe_priv,
197 x_scale,
198 y_scale,
199 &vpe_priv->cal_buffer,
200 output_tf);
201
202 return true;
203 }
204
color_update_degamma_tf(struct vpe_priv * vpe_priv,enum color_transfer_func color_input_tf,struct fixed31_32 x_scale,struct fixed31_32 y_scale,struct fixed31_32 y_bias,bool can_bypass,struct transfer_func * input_tf)205 static bool color_update_degamma_tf(struct vpe_priv *vpe_priv,
206 enum color_transfer_func color_input_tf, struct fixed31_32 x_scale,
207 struct fixed31_32 y_scale, struct fixed31_32 y_bias, bool can_bypass,
208 struct transfer_func *input_tf)
209 {
210 bool ret = true;
211 struct pwl_params *params = NULL;
212
213 if (can_bypass) {
214 input_tf->type = TF_TYPE_BYPASS;
215 return true;
216 }
217
218 input_tf->cm_gamma_type = CM_DEGAM;
219 input_tf->type = TF_TYPE_DISTRIBUTED_POINTS;
220 input_tf->start_base = y_bias;
221
222 switch (color_input_tf) {
223 case TRANSFER_FUNC_SRGB:
224 case TRANSFER_FUNC_BT709:
225 case TRANSFER_FUNC_BT1886:
226 case TRANSFER_FUNC_PQ2084:
227 case TRANSFER_FUNC_NORMALIZED_PQ:
228 case TRANSFER_FUNC_LINEAR_0_1:
229 case TRANSFER_FUNC_LINEAR_0_125:
230 input_tf->tf = color_input_tf;
231 break;
232 default:
233 VPE_ASSERT(0);
234 break;
235 }
236
237 if (!vpe_priv->init.debug.force_tf_calculation &&
238 x_scale.value == vpe_fixpt_one.value &&
239 y_scale.value == vpe_fixpt_one.value &&
240 y_bias.value == vpe_fixpt_zero.value)
241 vpe_priv->resource.get_tf_pwl_params(input_tf, ¶ms, CM_DEGAM);
242
243 if (params)
244 input_tf->use_pre_calculated_table = true;
245 else
246 input_tf->use_pre_calculated_table = false;
247
248 if (!input_tf->use_pre_calculated_table) {
249 vpe_color_calculate_degamma_params(vpe_priv,
250 x_scale,
251 y_scale,
252 input_tf);
253 }
254
255 return ret;
256 }
257
vpe_allocate_cm_memory(struct vpe_priv * vpe_priv,const struct vpe_build_param * param)258 static enum vpe_status vpe_allocate_cm_memory(struct vpe_priv *vpe_priv, const struct vpe_build_param *param) {
259
260 struct stream_ctx *stream_ctx;
261 struct output_ctx *output_ctx;
262 enum vpe_status status = VPE_STATUS_OK;
263
264 for (uint32_t stream_idx = 0; stream_idx < param->num_streams; stream_idx++) {
265 stream_ctx = &vpe_priv->stream_ctx[stream_idx];
266
267 if (!stream_ctx->input_cs) {
268 stream_ctx->input_cs =
269 (struct vpe_csc_matrix *)vpe_zalloc(sizeof(struct vpe_csc_matrix));
270 if (!stream_ctx->input_cs) {
271 vpe_log("err: out of memory for input cs!");
272 return VPE_STATUS_NO_MEMORY;
273 }
274 }
275
276 if (!stream_ctx->input_tf) {
277 stream_ctx->input_tf =
278 (struct transfer_func *)vpe_zalloc(sizeof(struct transfer_func));
279 if (!stream_ctx->input_tf) {
280 vpe_log("err: out of memory for input tf!");
281 return VPE_STATUS_NO_MEMORY;
282 }
283 }
284
285 if (!stream_ctx->bias_scale) {
286 stream_ctx->bias_scale =
287 (struct bias_and_scale *)vpe_zalloc(sizeof(struct bias_and_scale));
288 if (!stream_ctx->bias_scale) {
289 vpe_log("err: out of memory for bias and scale!");
290 return VPE_STATUS_NO_MEMORY;
291 }
292 }
293
294 if (!stream_ctx->gamut_remap) {
295 stream_ctx->gamut_remap = vpe_zalloc(sizeof(struct colorspace_transform));
296 if (!stream_ctx->gamut_remap) {
297 vpe_log("err: out of memory for gamut_remap!");
298 return VPE_STATUS_NO_MEMORY;
299 }
300 }
301 if (!stream_ctx->blend_tf) {
302 stream_ctx->blend_tf = vpe_zalloc(sizeof(struct transfer_func));
303 if (!stream_ctx->blend_tf) {
304 vpe_log("err: out of memory for blend tf!");
305 return VPE_STATUS_NO_MEMORY;
306 }
307 }
308 }
309
310 output_ctx = &vpe_priv->output_ctx;
311 if (!output_ctx->output_tf) {
312 output_ctx->output_tf =
313 (struct transfer_func *)vpe_zalloc(sizeof(struct transfer_func));
314 if (!output_ctx->output_tf) {
315 vpe_log("err: out of memory for output tf!");
316 return VPE_STATUS_NO_MEMORY;
317 }
318 }
319
320 return VPE_STATUS_OK;
321 }
322
color_get_icsc_cs(enum color_space ics)323 static enum color_space color_get_icsc_cs(enum color_space ics)
324 {
325 switch (ics) {
326 case COLOR_SPACE_SRGB:
327 case COLOR_SPACE_SRGB_LIMITED:
328 case COLOR_SPACE_MSREF_SCRGB:
329 case COLOR_SPACE_2020_RGB_FULLRANGE:
330 case COLOR_SPACE_2020_RGB_LIMITEDRANGE:
331 return COLOR_SPACE_SRGB;
332 case COLOR_SPACE_JFIF:
333 case COLOR_SPACE_YCBCR601:
334 case COLOR_SPACE_YCBCR601_LIMITED:
335 return COLOR_SPACE_YCBCR601;
336 case COLOR_SPACE_YCBCR709:
337 case COLOR_SPACE_YCBCR709_LIMITED:
338 return COLOR_SPACE_YCBCR709;
339 case COLOR_SPACE_2020_YCBCR:
340 case COLOR_SPACE_2020_YCBCR_LIMITED:
341 return COLOR_SPACE_2020_YCBCR;
342 default:
343 return COLOR_SPACE_UNKNOWN;
344 }
345 }
346
347 // return true is bypass can be done
color_update_input_cs(struct vpe_priv * vpe_priv,enum color_space in_cs,const struct vpe_color_adjust * adjustments,struct vpe_csc_matrix * input_cs,struct vpe_color_adjust * stream_clr_adjustments,struct fixed31_32 * matrix_scaling_factor)348 static bool color_update_input_cs(struct vpe_priv *vpe_priv, enum color_space in_cs,
349 const struct vpe_color_adjust *adjustments, struct vpe_csc_matrix *input_cs,
350 struct vpe_color_adjust *stream_clr_adjustments, struct fixed31_32 *matrix_scaling_factor)
351 {
352 int i, j;
353 bool use_adjustments = false;
354 int arr_size = sizeof(vpe_input_csc_matrix_fixed) / sizeof(struct vpe_csc_matrix);
355
356 input_cs->cs = COLOR_SPACE_UNKNOWN;
357 use_adjustments = vpe_use_csc_adjust(adjustments);
358 in_cs = color_get_icsc_cs(in_cs);
359
360 for (i = 0; i < arr_size; i++)
361 if (vpe_input_csc_matrix_fixed[i].cs == in_cs) {
362 input_cs->cs = vpe_input_csc_matrix_fixed[i].cs;
363 for (j = 0; j < 12; j++)
364 input_cs->regval[j] = vpe_input_csc_matrix_fixed[i].regval[j];
365 break;
366 }
367
368 if (i == arr_size) {
369 vpe_log("err: unknown cs not handled!");
370 return false;
371 }
372
373 if (use_adjustments && is_ycbcr(in_cs)) { // shader supports only yuv input for color
374 // adjustments
375 vpe_log("Apply color adjustments (contrast, saturation, hue, brightness)");
376 if (!vpe_color_calculate_input_cs(
377 vpe_priv, in_cs, adjustments, input_cs, matrix_scaling_factor))
378 return false;
379 *stream_clr_adjustments = *adjustments;
380 }
381
382 return true;
383 }
384
385 /* This function generates software points for the blnd gam programming block.
386 The logic for the blndgam/ogam programming sequence is a function of:
387 1. Output Range (Studio Full)
388 2. 3DLUT usage
389 3. Output format (HDR SDR)
390
391 SDR Out or studio range out
392 TM Case
393 BLNDGAM : NL -> NL*S + B
394 OGAM : Bypass
395 Non TM Case
396 BLNDGAM : L -> NL*S + B
397 OGAM : Bypass
398 Full range HDR Out
399 TM Case
400 BLNDGAM : NL -> L
401 OGAM : L -> NL
402 Non TM Case
403 BLNDGAM : Bypass
404 OGAM : L -> NL
405
406 */
407
vpe_update_blnd_gamma(struct vpe_priv * vpe_priv,const struct vpe_build_param * param,const struct vpe_tonemap_params * tm_params,struct transfer_func * blnd_tf)408 static enum vpe_status vpe_update_blnd_gamma(
409 struct vpe_priv *vpe_priv,
410 const struct vpe_build_param *param,
411 const struct vpe_tonemap_params *tm_params,
412 struct transfer_func *blnd_tf)
413 {
414
415 struct output_ctx *output_ctx;
416 struct vpe_color_space tm_out_cs;
417 struct fixed31_32 x_scale = vpe_fixpt_one;
418 struct fixed31_32 y_scale = vpe_fixpt_one;
419 struct fixed31_32 y_bias = vpe_fixpt_zero;
420 bool is_studio = false;
421 bool can_bypass = false;
422 bool lut3d_enabled = false;
423 enum color_space cs = COLOR_SPACE_2020_RGB_FULLRANGE;
424 enum color_transfer_func tf = TRANSFER_FUNC_LINEAR_0_1;
425 enum vpe_status status = VPE_STATUS_OK;
426
427 is_studio = (param->dst_surface.cs.range == VPE_COLOR_RANGE_STUDIO);
428 output_ctx = &vpe_priv->output_ctx;
429 lut3d_enabled = tm_params->UID != 0 || tm_params->enable_3dlut;
430
431 if (is_studio) {
432
433 if (vpe_is_rgb8(param->dst_surface.format)) {
434 y_scale = STUDIO_RANGE_SCALE_8_BIT;
435 y_bias = STUDIO_RANGE_FOOT_ROOM_8_BIT;
436 }
437 else {
438
439 y_scale = STUDIO_RANGE_SCALE_10_BIT;
440 y_bias = STUDIO_RANGE_FOOT_ROOM_10_BIT;
441 }
442 }
443
444 //If SDR out -> Blend should be NL
445 //If studio out -> No choice but to blend in NL
446 if (!vpe_is_HDR(output_ctx->tf) || is_studio) {
447 if (lut3d_enabled) {
448 tf = TRANSFER_FUNC_LINEAR_0_1;
449 }
450 else {
451 tf = output_ctx->tf;
452 }
453
454 color_update_regamma_tf(vpe_priv,
455 tf,
456 x_scale,
457 y_scale,
458 y_bias,
459 can_bypass,
460 blnd_tf);
461 }
462 else {
463
464 if (lut3d_enabled) {
465 vpe_color_build_tm_cs(tm_params, param->dst_surface, &tm_out_cs);
466 vpe_color_get_color_space_and_tf(&tm_out_cs, &cs, &tf);
467 }
468 else {
469 can_bypass = true;
470 }
471
472 color_update_degamma_tf(vpe_priv,
473 tf,
474 x_scale,
475 y_scale,
476 y_bias,
477 can_bypass,
478 blnd_tf);
479 }
480 return status;
481 }
482
483 /* This function generates software points for the ogam gamma programming block.
484 The logic for the blndgam/ogam programming sequence is a function of:
485 1. Output Range (Studio Full)
486 2. 3DLUT usage
487 3. Output format (HDR SDR)
488
489 SDR Out or studio range out
490 TM Case
491 BLNDGAM : NL -> NL*S + B
492 OGAM : Bypass
493 Non TM Case
494 BLNDGAM : L -> NL*S + B
495 OGAM : Bypass
496 Full range HDR Out
497 TM Case
498 BLNDGAM : NL -> L
499 OGAM : L -> NL
500 Non TM Case
501 BLNDGAM : Bypass
502 OGAM : L -> NL
503
504 */
vpe_update_output_gamma(struct vpe_priv * vpe_priv,const struct vpe_build_param * param,struct transfer_func * output_tf)505 static enum vpe_status vpe_update_output_gamma(
506 struct vpe_priv *vpe_priv,
507 const struct vpe_build_param *param,
508 struct transfer_func *output_tf)
509 {
510 bool can_bypass = false;
511 struct output_ctx *output_ctx = &vpe_priv->output_ctx;
512 bool is_studio = (param->dst_surface.cs.range == VPE_COLOR_RANGE_STUDIO);
513 enum vpe_status status = VPE_STATUS_OK;
514
515 if (vpe_is_HDR(output_ctx->tf) && !is_studio)
516 can_bypass = false; //Blending is done in linear light so ogam needs to handle the regam
517 else
518 can_bypass = true;
519
520 color_update_regamma_tf(vpe_priv,
521 output_ctx->tf,
522 vpe_fixpt_one,
523 vpe_fixpt_one,
524 vpe_fixpt_zero,
525 can_bypass,
526 output_tf);
527
528 return status;
529 }
530
vpe_use_csc_adjust(const struct vpe_color_adjust * adjustments)531 bool vpe_use_csc_adjust(const struct vpe_color_adjust *adjustments)
532 {
533 float epsilon = 0.001f; // steps are 1.0f or 0.01f, so should be plenty
534
535 // see vpe_types.h and vpe_color_adjust definition for VpBlt ranges
536
537 // default brightness = 0
538 if (adjustments->brightness > epsilon || adjustments->brightness < -epsilon)
539 return true;
540
541 // default contrast = 1
542 if (adjustments->contrast > 1 + epsilon || adjustments->contrast < 1 - epsilon)
543 return true;
544
545 // default saturation = 1
546 if (adjustments->saturation > 1 + epsilon || adjustments->saturation < 1 - epsilon)
547 return true;
548
549 // default hue = 0
550 if (adjustments->hue > epsilon || adjustments->hue < -epsilon)
551 return true;
552
553 return false;
554 }
555
556 /* Bias and Scale reference table
557 Encoding Bpp Format Data Range Expansion Bias Scale
558 aRGB 32bpp 8888 Full Zero 0 256/255
559 8888 Limited Zero -16/256 256/(235-16)
560 2101010 Full Zero 0 1024/1023
561 2101010 Limited Zero -64/1024 1024/(940-64)
562 2101010 XR bias Zero -384/1024 1024/510 // not used
563 64bpp fixed 10bpc Full Zero 0 1024/1023 // do we have these?
564 10 bpc limited zero -64/1024 1024/(940-64)
565 12 bpc Full Zero 0 4096/4095
566 12 bpc Limited Zero -256/4096 4096/(3760-256)
567 aCrYCb 32bpp 8888 Full Zero 0 256/255
568 8888 Limited Zero Y:-16/256 Y:256/(235-16)
569 C:-128/256 C:256/(240-16) // See notes
570 below 2101010 Full Zero 0 1024/1023 2101010 Limited Zero
571 Y:-64/1024 Y:1024/(940-64) C:-512/1024 C:1024(960-64) 64bpp fixed 10bpc Full Zero 0
572 1024/1023 10 bpc Limited Zero Y:-64/1024 Y:1024/(940-64) C:-512/1024
573 C:1024(960-64) // See notes below 12 bpc Full Zero 0 4096/4095 12
574 bpc Limited Zero Y:-256/4096 Y:4096/(3760-256) C:-2048/4096 C:4096/(3840-256) //
575 See notes below
576
577 The bias_c we use here in the function are diff with the above table from hw team
578 because the table is to run with CSC matrix which expect chroma
579 from -0.5~+0.5.
580 However the csc matrix we use in ICSC is expecting chroma value
581 from 0.0~1.0.
582 Hence we need a bias for chroma to output a range from 0.0~1.0 instead.
583 So we use the same value as luma (Y) which expects range from 0~1.0 already.
584 */
build_scale_and_bias(struct bias_and_scale * bias_and_scale,const struct vpe_color_space * vcs,enum vpe_surface_pixel_format format)585 static bool build_scale_and_bias(struct bias_and_scale *bias_and_scale,
586 const struct vpe_color_space *vcs, enum vpe_surface_pixel_format format)
587 {
588 struct fixed31_32 scale = vpe_fixpt_one; // RGB or Y
589 struct fixed31_32 scale_c = vpe_fixpt_one; // Cb/Cr
590 struct fixed31_32 bias = vpe_fixpt_zero; // RGB or Y
591 struct fixed31_32 bias_c = vpe_fixpt_from_fraction(-1, 2); // Cb/Cr
592 bool is_chroma_different = false;
593
594 struct custom_float_format fmt;
595 fmt.exponenta_bits = 6;
596 fmt.mantissa_bits = 12;
597 fmt.sign = true;
598
599 if (vpe_is_rgb8(format)) {
600 if (vcs->range == VPE_COLOR_RANGE_FULL) {
601 scale = vpe_fixpt_from_fraction(256, 255);
602 } else if (vcs->range == VPE_COLOR_RANGE_STUDIO) {
603 scale = vpe_fixpt_from_fraction(256, 235 - 16);
604 bias = vpe_fixpt_from_fraction(-16, 256);
605 } // else report error? here just go with default (1.0, 0.0)
606 } else if (vpe_is_rgb10(format)) {
607 if (vcs->range == VPE_COLOR_RANGE_FULL) {
608 scale = vpe_fixpt_from_fraction(1024, 1023);
609 } else if (vcs->range == VPE_COLOR_RANGE_STUDIO) {
610 scale = vpe_fixpt_from_fraction(1024, 940 - 64);
611 bias = vpe_fixpt_from_fraction(-64, 1024);
612 } // else report error? here just go with default (1.0, 0.0)
613 } else if (vpe_is_yuv420_8(format) || vpe_is_yuv444_8(format)) {
614 if (vcs->range == VPE_COLOR_RANGE_FULL) {
615 scale = vpe_fixpt_from_fraction(256, 255);
616 } else if (vcs->range == VPE_COLOR_RANGE_STUDIO) {
617 scale = vpe_fixpt_from_fraction(256, 235 - 16);
618 bias = vpe_fixpt_from_fraction(-16, 256);
619 scale_c = vpe_fixpt_from_fraction(256, 240 - 16);
620 bias_c = vpe_fixpt_from_fraction(-16, 256); // See notes in function comment
621 is_chroma_different = true;
622 } // else report error? not sure if default is right
623 } else if (vpe_is_yuv420_10(format) || vpe_is_yuv444_10(format)) {
624 if (vcs->range == VPE_COLOR_RANGE_FULL) {
625 scale = vpe_fixpt_from_fraction(1024, 1023);
626 } else if (vcs->range == VPE_COLOR_RANGE_STUDIO) {
627 scale = vpe_fixpt_from_fraction(1024, 940 - 64);
628 bias = vpe_fixpt_from_fraction(-64, 1024);
629 scale_c = vpe_fixpt_from_fraction(1024, 960 - 64);
630 bias_c = vpe_fixpt_from_fraction(-64, 1024); // See notes in function comment
631 is_chroma_different = true;
632 } // else report error? not sure if default is right
633 }
634
635 vpe_convert_to_custom_float_format(scale, &fmt, &bias_and_scale->scale_green);
636 vpe_convert_to_custom_float_format(bias, &fmt, &bias_and_scale->bias_green);
637
638 // see definition of scale/bias and scale_c/bias_c
639 // RGB formats only have scale/bias since all color channels are the same
640 // YCbCr have scale/bias for Y (in HW maps to G) and scale_c/bias_c for CrCb (mapping to R,B)
641 if (!is_chroma_different) {
642 bias_and_scale->scale_red = bias_and_scale->scale_green;
643 bias_and_scale->scale_blue = bias_and_scale->scale_green;
644 bias_and_scale->bias_red = bias_and_scale->bias_green;
645 bias_and_scale->bias_blue = bias_and_scale->bias_green;
646 } else {
647 vpe_convert_to_custom_float_format(scale_c, &fmt, &bias_and_scale->scale_red);
648 vpe_convert_to_custom_float_format(bias_c, &fmt, &bias_and_scale->bias_red);
649 bias_and_scale->scale_blue = bias_and_scale->scale_red;
650 bias_and_scale->bias_blue = bias_and_scale->bias_red;
651 }
652
653 return true;
654 }
655
vpe_color_build_tm_cs(const struct vpe_tonemap_params * tm_params,struct vpe_surface_info surface_info,struct vpe_color_space * tm_out_cs)656 enum vpe_status vpe_color_build_tm_cs(const struct vpe_tonemap_params *tm_params,
657 struct vpe_surface_info surface_info, struct vpe_color_space *tm_out_cs)
658 {
659 tm_out_cs->tf = tm_params->lut_out_tf;
660 tm_out_cs->primaries = tm_params->lut_out_gamut;
661 tm_out_cs->encoding = surface_info.cs.encoding;
662 tm_out_cs->range = VPE_COLOR_RANGE_FULL; // surface_info.cs.range;
663 tm_out_cs->cositing = VPE_CHROMA_COSITING_NONE; // surface_info.cs.cositing;
664
665 return VPE_STATUS_OK;
666 }
667
vpe_color_update_color_space_and_tf(struct vpe_priv * vpe_priv,const struct vpe_build_param * param)668 enum vpe_status vpe_color_update_color_space_and_tf(
669 struct vpe_priv *vpe_priv, const struct vpe_build_param *param)
670 {
671 uint32_t stream_idx;
672 struct stream_ctx *stream_ctx;
673 struct fixed31_32 new_matrix_scaling_factor;
674 struct output_ctx *output_ctx = &vpe_priv->output_ctx;
675 enum vpe_status status = VPE_STATUS_OK;
676
677 status = vpe_allocate_cm_memory(vpe_priv, param);
678 if (status == VPE_STATUS_OK) {
679
680 color_check_output_cm_update(vpe_priv, &vpe_priv->output_ctx.surface.cs);
681
682 for (stream_idx = 0; stream_idx < param->num_streams; stream_idx++) {
683
684 new_matrix_scaling_factor = vpe_fixpt_one;
685 stream_ctx = &vpe_priv->stream_ctx[stream_idx];
686
687 color_check_input_cm_update(vpe_priv, stream_ctx,
688 ¶m->streams[stream_idx].surface_info.cs, ¶m->streams[stream_idx].color_adj,
689 param->streams[stream_idx].tm_params.UID != 0 || param->streams[stream_idx].tm_params.enable_3dlut);
690
691 build_scale_and_bias(stream_ctx->bias_scale,
692 ¶m->streams[stream_idx].surface_info.cs,
693 param->streams[stream_idx].surface_info.format);
694
695 if (stream_ctx->dirty_bits.color_space) {
696 if (!color_update_input_cs(vpe_priv, stream_ctx->cs,
697 &stream_ctx->stream.color_adj, stream_ctx->input_cs,
698 &stream_ctx->color_adjustments, &new_matrix_scaling_factor)) {
699 vpe_log("err: input cs not being programmed!");
700 }
701 else {
702 if ((vpe_priv->scale_yuv_matrix) && // the option to scale the matrix yuv to rgb is
703 // on
704 (new_matrix_scaling_factor.value !=
705 vpe_priv->stream_ctx->tf_scaling_factor.value)) {
706 vpe_priv->stream_ctx->tf_scaling_factor = new_matrix_scaling_factor;
707 stream_ctx->dirty_bits.transfer_function = 1; // force tf recalculation
708 }
709 }
710 }
711
712 if (stream_ctx->dirty_bits.transfer_function) {
713 color_update_degamma_tf(vpe_priv, stream_ctx->tf,
714 vpe_priv->stream_ctx->tf_scaling_factor,
715 vpe_fixpt_one,
716 vpe_fixpt_zero,
717 stream_ctx->stream.tm_params.UID != 0 || stream_ctx->stream.tm_params.enable_3dlut, // By Pass degamma if 3DLUT is enabled
718 stream_ctx->input_tf);
719 }
720
721 if (stream_ctx->dirty_bits.color_space || output_ctx->dirty_bits.color_space) {
722 status = vpe_color_update_gamut(vpe_priv, stream_ctx->cs, output_ctx->cs,
723 stream_ctx->gamut_remap,
724 stream_ctx->stream.tm_params.UID != 0 || stream_ctx->stream.tm_params.enable_3dlut);
725 }
726
727
728 if (output_ctx->dirty_bits.transfer_function ||
729 output_ctx->dirty_bits.color_space ||
730 stream_ctx->update_3dlut) {
731 vpe_update_blnd_gamma(vpe_priv, param, &stream_ctx->stream.tm_params, stream_ctx->blend_tf);
732 }
733 }
734
735 if (status == VPE_STATUS_OK) {
736 if (output_ctx->dirty_bits.transfer_function ||
737 output_ctx->dirty_bits.color_space) {
738 vpe_update_output_gamma(vpe_priv, param, output_ctx->output_tf);
739 }
740 }
741
742 }
743 return status;
744 }
745
vpe_color_tm_update_hdr_mult(uint16_t shaper_in_exp_max,uint32_t peak_white,struct fixed31_32 * hdr_multiplier,bool enable3dlut)746 enum vpe_status vpe_color_tm_update_hdr_mult(uint16_t shaper_in_exp_max, uint32_t peak_white,
747 struct fixed31_32 *hdr_multiplier, bool enable3dlut)
748 {
749 if (enable3dlut) {
750 struct fixed31_32 shaper_in_gain;
751 struct fixed31_32 pq_norm_gain;
752
753 // HDRMULT = 2^shaper_in_exp_max*(1/PQ(x))
754 shaper_in_gain = vpe_fixpt_from_int((long long)1 << shaper_in_exp_max);
755 vpe_compute_pq(vpe_fixpt_from_fraction((long long)peak_white, 10000), &pq_norm_gain);
756
757 *hdr_multiplier = vpe_fixpt_div(shaper_in_gain, pq_norm_gain);
758 } else {
759 *hdr_multiplier = vpe_fixpt_one;
760 }
761
762 return VPE_STATUS_OK;
763 }
764
vpe_color_update_shaper(uint16_t shaper_in_exp_max,struct transfer_func * shaper_func,bool enable_3dlut)765 enum vpe_status vpe_color_update_shaper(
766 uint16_t shaper_in_exp_max, struct transfer_func *shaper_func, bool enable_3dlut)
767
768 {
769 if (!enable_3dlut) {
770 shaper_func->type = TF_TYPE_BYPASS;
771 return VPE_STATUS_OK;
772 }
773 struct vpe_shaper_setup_in shaper_in;
774
775 shaper_in.shaper_in_max = 1 << 16;
776 shaper_in.use_const_hdr_mult = false; // can't be true. Fix is required.
777
778 shaper_func->type = TF_TYPE_HWPWL;
779 shaper_func->tf = TRANSFER_FUNC_LINEAR_0_1;
780 return vpe_build_shaper(&shaper_in, &shaper_func->pwl);
781 }
782
vpe_color_update_movable_cm(struct vpe_priv * vpe_priv,const struct vpe_build_param * param)783 enum vpe_status vpe_color_update_movable_cm(
784 struct vpe_priv *vpe_priv, const struct vpe_build_param *param)
785 {
786 enum vpe_status ret = VPE_STATUS_OK;
787
788 uint32_t stream_idx;
789 struct stream_ctx *stream_ctx;
790 struct output_ctx *output_ctx = &vpe_priv->output_ctx;
791
792 for (stream_idx = 0; stream_idx < param->num_streams; stream_idx++) {
793 stream_ctx = &vpe_priv->stream_ctx[stream_idx];
794
795 bool enable_3dlut = stream_ctx->stream.tm_params.UID != 0 || stream_ctx->stream.tm_params.enable_3dlut;
796
797 if (stream_ctx->update_3dlut || stream_ctx->UID_3DLUT != stream_ctx->stream.tm_params.UID) {
798
799 uint32_t shaper_norm_factor;
800 struct vpe_color_space tm_out_cs;
801 enum color_space out_lut_cs;
802 enum color_transfer_func tf;
803
804 if (!stream_ctx->in_shaper_func) {
805 stream_ctx->in_shaper_func = vpe_zalloc(sizeof(struct transfer_func));
806 if (!stream_ctx->in_shaper_func) {
807 vpe_log("err: out of memory for shaper tf!");
808 ret = VPE_STATUS_NO_MEMORY;
809 goto exit;
810 }
811 }
812
813 if (!stream_ctx->blend_tf) {
814 stream_ctx->blend_tf = vpe_zalloc(sizeof(struct transfer_func));
815 if (!stream_ctx->blend_tf) {
816 vpe_log("err: out of memory for blend/post1d tf!");
817 ret = VPE_STATUS_NO_MEMORY;
818 goto exit;
819 }
820 }
821
822 if (!stream_ctx->lut3d_func) {
823 stream_ctx->lut3d_func = vpe_zalloc(sizeof(struct vpe_3dlut));
824 if (!stream_ctx->lut3d_func) {
825 vpe_log("err: out of memory for 3d lut!");
826 ret = VPE_STATUS_NO_MEMORY;
827 goto exit;
828 }
829 }
830
831 if (!output_ctx->gamut_remap) {
832 output_ctx->gamut_remap = vpe_zalloc(sizeof(struct colorspace_transform));
833 if (!output_ctx->gamut_remap) {
834 vpe_log("err: out of memory for post blend gamut remap!");
835 ret = VPE_STATUS_NO_MEMORY;
836 goto exit;
837 }
838 }
839
840 //Blendgam is updated by output vpe_update_output_gamma_sequence
841
842 get_shaper_norm_factor(
843 ¶m->streams[stream_idx].tm_params, stream_ctx, &shaper_norm_factor);
844
845 vpe_color_tm_update_hdr_mult(SHAPER_EXP_MAX_IN, shaper_norm_factor,
846 &stream_ctx->lut3d_func->hdr_multiplier, enable_3dlut);
847
848 vpe_color_update_shaper(SHAPER_EXP_MAX_IN, stream_ctx->in_shaper_func, enable_3dlut);
849
850 vpe_color_build_tm_cs(&stream_ctx->stream.tm_params, vpe_priv->output_ctx.surface, &tm_out_cs);
851
852 vpe_color_get_color_space_and_tf(&tm_out_cs, &out_lut_cs, &tf);
853
854 vpe_color_update_gamut(vpe_priv, out_lut_cs, vpe_priv->output_ctx.cs,
855 output_ctx->gamut_remap, !enable_3dlut);
856
857 vpe_convert_to_tetrahedral(vpe_priv, param->streams[stream_idx].tm_params.lut_data,
858 stream_ctx->lut3d_func, enable_3dlut);
859
860 stream_ctx->update_3dlut = false;
861 stream_ctx->UID_3DLUT = param->streams[stream_idx].tm_params.UID;
862 }
863 }
864 exit:
865 return ret;
866 }
867
vpe_color_get_color_space_and_tf(const struct vpe_color_space * vcs,enum color_space * cs,enum color_transfer_func * tf)868 void vpe_color_get_color_space_and_tf(
869 const struct vpe_color_space *vcs, enum color_space *cs, enum color_transfer_func *tf)
870 {
871 enum vpe_color_range colorRange = vcs->range;
872
873 *cs = COLOR_SPACE_UNKNOWN;
874 *tf = TRANSFER_FUNC_UNKNOWN;
875
876 switch (vcs->tf) {
877 case VPE_TF_G22:
878 *tf = TRANSFER_FUNC_SRGB;
879 break;
880 case VPE_TF_G24:
881 *tf = TRANSFER_FUNC_BT1886;
882 break;
883 case VPE_TF_PQ:
884 *tf = TRANSFER_FUNC_PQ2084;
885 break;
886 case VPE_TF_PQ_NORMALIZED:
887 *tf = TRANSFER_FUNC_NORMALIZED_PQ;
888 break;
889 case VPE_TF_G10:
890 *tf = TRANSFER_FUNC_LINEAR_0_125;
891 break;
892 case VPE_TF_SRGB:
893 *tf = TRANSFER_FUNC_SRGB;
894 break;
895 case VPE_TF_BT709:
896 *tf = TRANSFER_FUNC_BT709;
897 break;
898 case VPE_TF_HLG:
899 *tf = TRANSFER_FUNC_HLG;
900 break;
901 default:
902 break;
903 }
904
905 if (vcs->encoding == VPE_PIXEL_ENCODING_YCbCr) {
906 switch (vcs->tf) {
907 case VPE_TF_G22:
908 *tf = TRANSFER_FUNC_BT709;
909 break;
910 default:
911 break;
912 }
913
914 switch (vcs->primaries) {
915 case VPE_PRIMARIES_BT601:
916 *cs = colorRange == VPE_COLOR_RANGE_FULL ? COLOR_SPACE_YCBCR601
917 : COLOR_SPACE_YCBCR601_LIMITED;
918 break;
919 case VPE_PRIMARIES_BT709:
920 *cs = colorRange == VPE_COLOR_RANGE_FULL ? COLOR_SPACE_YCBCR709
921 : COLOR_SPACE_YCBCR709_LIMITED;
922 break;
923 case VPE_PRIMARIES_BT2020:
924 *cs = colorRange == VPE_COLOR_RANGE_FULL ? COLOR_SPACE_2020_YCBCR
925 : COLOR_SPACE_2020_YCBCR_LIMITED;
926 break;
927 case VPE_PRIMARIES_JFIF:
928 *cs = colorRange == VPE_COLOR_RANGE_FULL ? COLOR_SPACE_JFIF : COLOR_SPACE_UNKNOWN;
929 break;
930 default:
931 break;
932 }
933 } else {
934 switch (vcs->primaries) {
935 case VPE_PRIMARIES_BT709:
936 if (vcs->tf == VPE_TF_G10) {
937 *cs = COLOR_SPACE_MSREF_SCRGB;
938 } else {
939 *cs = colorRange == VPE_COLOR_RANGE_FULL ? COLOR_SPACE_SRGB
940 : COLOR_SPACE_SRGB_LIMITED;
941 }
942 break;
943 case VPE_PRIMARIES_BT2020:
944 *cs = colorRange == VPE_COLOR_RANGE_FULL ? COLOR_SPACE_2020_RGB_FULLRANGE
945 : COLOR_SPACE_2020_RGB_LIMITEDRANGE;
946 break;
947 default:
948 break;
949 }
950 }
951 }
952
vpe_is_rgb_equal(const struct pwl_result_data * rgb,uint32_t num)953 bool vpe_is_rgb_equal(const struct pwl_result_data *rgb, uint32_t num)
954 {
955 uint32_t i;
956 bool ret = true;
957
958 for (i = 0; i < num; i++) {
959 if (rgb[i].red_reg != rgb[i].green_reg || rgb[i].blue_reg != rgb[i].red_reg ||
960 rgb[i].blue_reg != rgb[i].green_reg) {
961 ret = false;
962 break;
963 }
964 }
965 return ret;
966 }
967
vpe_convert_full_range_color_enum(enum color_space * cs)968 void vpe_convert_full_range_color_enum(enum color_space *cs)
969 {
970 switch (*cs) {
971 case COLOR_SPACE_YCBCR601_LIMITED:
972 *cs = COLOR_SPACE_YCBCR601;
973 break;
974 case COLOR_SPACE_YCBCR709_LIMITED:
975 *cs = COLOR_SPACE_YCBCR709;
976 break;
977 case COLOR_SPACE_2020_YCBCR_LIMITED:
978 *cs = COLOR_SPACE_2020_YCBCR;
979 break;
980 case COLOR_SPACE_SRGB_LIMITED:
981 *cs = COLOR_SPACE_SRGB;
982 break;
983 case COLOR_SPACE_2020_RGB_LIMITEDRANGE:
984 *cs = COLOR_SPACE_2020_RGB_FULLRANGE;
985 break;
986 default:
987 break;
988 }
989 }
990
vpe_is_HDR(enum color_transfer_func tf)991 bool vpe_is_HDR(enum color_transfer_func tf)
992 {
993
994 return (tf == TRANSFER_FUNC_PQ2084 || tf == TRANSFER_FUNC_LINEAR_0_125 || tf == TRANSFER_FUNC_HLG);
995 }
996
997 /*
998 *
999 * Pixel processing in VPE can be divided int two main paths. Tone maping cases and non tone mapping
1000 * cases. The gain factor supplied by the below function is only applied in the non-tone mapping
1001 * path.
1002 *
1003 * The gain is used to scale the white point in SDR<->HDR conversions.
1004 *
1005 * The policy is as follows:
1006 * HDR -> SDR (None tone mapping case): Map max input pixel value indicated by HDR meta data to
1007 * value of 1. SDR-> HDR : Map nominal value of 1 to display brightness indicated by metadata.
1008 *
1009 * Table outlining handling for full combination can be found in VPE Wolfpack
1010 */
vpe_color_update_whitepoint(const struct vpe_priv * vpe_priv,const struct vpe_build_param * param)1011 enum vpe_status vpe_color_update_whitepoint(
1012 const struct vpe_priv *vpe_priv, const struct vpe_build_param *param)
1013 {
1014
1015 struct stream_ctx *stream = vpe_priv->stream_ctx;
1016 const struct output_ctx *output_ctx = &vpe_priv->output_ctx;
1017 const struct vpe_color_space *vpe_cs = &stream->stream.surface_info.cs;
1018 bool output_isHDR = vpe_is_HDR(vpe_priv->output_ctx.tf);
1019 bool input_isHDR = false;
1020 bool isYCbCr = false;
1021 bool isG24 = false;
1022
1023 for (unsigned int stream_index = 0; stream_index < vpe_priv->num_streams; stream_index++) {
1024
1025 input_isHDR = vpe_is_HDR(stream->tf);
1026 isYCbCr = (vpe_cs->encoding == VPE_PIXEL_ENCODING_YCbCr);
1027 isG24 = (vpe_cs->tf == VPE_TF_G24);
1028
1029 if (!input_isHDR && output_isHDR) {
1030 int sdrWhiteLevel = (isYCbCr || isG24) ? SDR_VIDEO_WHITE_POINT : SDR_WHITE_POINT;
1031 stream->white_point_gain = vpe_fixpt_from_fraction(sdrWhiteLevel, 10000);
1032 } else if (input_isHDR && !output_isHDR) {
1033
1034 stream->white_point_gain = stream->stream.hdr_metadata.max_mastering != 0
1035 ? vpe_fixpt_from_fraction(HDR_PEAK_WHITE,
1036 stream->stream.hdr_metadata.max_mastering)
1037 : vpe_fixpt_one;
1038 } else {
1039 stream->white_point_gain = vpe_fixpt_one;
1040 }
1041 stream++;
1042 }
1043 return VPE_STATUS_OK;
1044 }
1045