Welcome to mirror list, hosted at ThFree Co, Russian Federation.

eevee_film_lib.glsl « shaders « eevee_next « engines « draw « blender « source - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: b286836e8dffa8e9e2f091d96e040e33aa74b18d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713

/**
 * Film accumulation utils functions.
 **/

#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_math_geom_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_camera_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_velocity_lib.glsl)

/* Return scene linear Z depth from the camera or radial depth for panoramic cameras. */
float film_depth_convert_to_scene(float depth)
{
  if (false /* Panoramic */) {
    /* TODO */
    return 1.0;
  }
  return abs(get_view_z_from_depth(depth));
}

vec3 film_YCoCg_from_scene_linear(vec3 rgb_color)
{
  const mat3 colorspace_tx = transpose(mat3(vec3(1, 2, 1),     /* Y */
                                            vec3(2, 0, -2),    /* Co */
                                            vec3(-1, 2, -1))); /* Cg */
  return colorspace_tx * rgb_color;
}

vec4 film_YCoCg_from_scene_linear(vec4 rgba_color)
{
  return vec4(film_YCoCg_from_scene_linear(rgba_color.rgb), rgba_color.a);
}

vec3 film_scene_linear_from_YCoCg(vec3 ycocg_color)
{
  float Y = ycocg_color.x;
  float Co = ycocg_color.y;
  float Cg = ycocg_color.z;

  vec3 rgb_color;
  rgb_color.r = Y + Co - Cg;
  rgb_color.g = Y + Cg;
  rgb_color.b = Y - Co - Cg;
  return rgb_color * 0.25;
}

/* Load a texture sample in a specific format. Combined pass needs to use this. */
vec4 film_texelfetch_as_YCoCg_opacity(sampler2D tx, ivec2 texel)
{
  vec4 color = texelFetch(combined_tx, texel, 0);
  /* Convert transmittance to opacity. */
  color.a = saturate(1.0 - color.a);
  /* Transform to YCoCg for accumulation. */
  color.rgb = film_YCoCg_from_scene_linear(color.rgb);
  return color;
}

/* Returns a weight based on Luma to reduce the flickering introduced by high energy pixels. */
float film_luma_weight(float luma)
{
  /* Slide 20 of "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014. */
  /* To preserve more details in dark areas, we use a bigger bias. */
  return 1.0 / (4.0 + luma * film_buf.exposure_scale);
}

/* -------------------------------------------------------------------- */
/** \name Filter
 * \{ */

FilmSample film_sample_get(int sample_n, ivec2 texel_film)
{
#ifdef PANORAMIC
  /* TODO(fclem): Panoramic projection will be more complex. The samples will have to be retrieve
   * at runtime, maybe by scanning a whole region. Offset and weight will have to be computed by
   * reprojecting the incoming pixel data into film pixel space. */
#else

#  ifdef SCALED_RENDERING
  texel_film /= film_buf.scaling_factor;
#  endif

  FilmSample film_sample = film_buf.samples[sample_n];
  film_sample.texel += texel_film + film_buf.offset;
  /* Use extend on borders. */
  film_sample.texel = clamp(film_sample.texel, ivec2(0, 0), film_buf.render_extent - 1);

  /* TODO(fclem): Panoramic projection will need to compute the sample weight in the shader
   * instead of precomputing it on CPU. */
#  ifdef SCALED_RENDERING
  /* We need to compute the real distance and weight since a sample
   * can be used by many final pixel. */
  vec2 offset = film_buf.subpixel_offset - vec2(texel_film % film_buf.scaling_factor);
  film_sample.weight = film_filter_weight(film_buf.filter_size, len_squared(offset));
#  endif

#endif /* PANORAMIC */

  /* Always return a weight above 0 to avoid blind spots between samples. */
  film_sample.weight = max(film_sample.weight, 1e-6);

  return film_sample;
}

/* Returns the combined weights of all samples affecting this film pixel. */
float film_weight_accumulation(ivec2 texel_film)
{
#if 0 /* TODO(fclem): Reference implementation, also needed for panoramic cameras. */
  float weight = 0.0;
  for (int i = 0; i < film_buf.samples_len; i++) {
    weight += film_sample_get(i, texel_film).weight;
  }
  return weight;
#endif
  return film_buf.samples_weight_total;
}

void film_sample_accum(FilmSample samp, int pass_id, sampler2D tex, inout vec4 accum)
{
  if (pass_id == -1) {
    return;
  }
  accum += texelFetch(tex, samp.texel, 0) * samp.weight;
}

void film_sample_accum(FilmSample samp, int pass_id, sampler2D tex, inout float accum)
{
  if (pass_id == -1) {
    return;
  }
  accum += texelFetch(tex, samp.texel, 0).x * samp.weight;
}

void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout vec4 accum)
{
  if (pass_id == -1) {
    return;
  }
  accum += texelFetch(tex, ivec3(samp.texel, pass_id), 0) * samp.weight;
}

void film_sample_accum(FilmSample samp, int pass_id, sampler2DArray tex, inout float accum)
{
  if (pass_id == -1) {
    return;
  }
  accum += texelFetch(tex, ivec3(samp.texel, pass_id), 0).x * samp.weight;
}

void film_sample_accum_mist(FilmSample samp, inout float accum)
{
  if (film_buf.mist_id == -1) {
    return;
  }
  float depth = texelFetch(depth_tx, samp.texel, 0).x;
  vec2 uv = (vec2(samp.texel) + 0.5) / textureSize(depth_tx, 0).xy;
  vec3 vP = get_view_space_from_depth(uv, depth);
  bool is_persp = ProjectionMatrix[3][3] == 0.0;
  float mist = (is_persp) ? length(vP) : abs(vP.z);
  /* Remap to 0..1 range. */
  mist = saturate(mist * film_buf.mist_scale + film_buf.mist_bias);
  /* Falloff. */
  mist = pow(mist, film_buf.mist_exponent);
  accum += mist * samp.weight;
}

void film_sample_accum_combined(FilmSample samp, inout vec4 accum, inout float weight_accum)
{
  if (film_buf.combined_id == -1) {
    return;
  }
  vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, samp.texel);

  /* Weight by luma to remove fireflies. */
  float weight = film_luma_weight(color.x) * samp.weight;

  accum += color * weight;
  weight_accum += weight;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Load/Store Data
 * \{ */

#define WEIGHT_lAYER_ACCUMULATION 0
#define WEIGHT_lAYER_DISTANCE 1

/* Returns the distance used to store nearest interpolation data. */
float film_distance_load(ivec2 texel)
{
  /* Repeat texture coordinates as the weight can be optimized to a small portion of the film. */
  texel = texel % imageSize(in_weight_img).xy;

  if (!film_buf.use_history || film_buf.use_reprojection) {
    return 1.0e16;
  }
  return imageLoad(in_weight_img, ivec3(texel, WEIGHT_lAYER_DISTANCE)).x;
}

float film_weight_load(ivec2 texel)
{
  /* Repeat texture coordinates as the weight can be optimized to a small portion of the film. */
  texel = texel % imageSize(in_weight_img).xy;

  if (!film_buf.use_history || film_buf.use_reprojection) {
    return 0.0;
  }
  return imageLoad(in_weight_img, ivec3(texel, WEIGHT_lAYER_ACCUMULATION)).x;
}

/* Returns motion in pixel space to retrieve the pixel history. */
vec2 film_pixel_history_motion_vector(ivec2 texel_sample)
{
  /**
   * Dilate velocity by using the nearest pixel in a cross pattern.
   * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 27)
   */
  const ivec2 corners[4] = ivec2[4](ivec2(-2, -2), ivec2(2, -2), ivec2(-2, 2), ivec2(2, 2));
  float min_depth = texelFetch(depth_tx, texel_sample, 0).x;
  ivec2 nearest_texel = texel_sample;
  for (int i = 0; i < 4; i++) {
    ivec2 texel = clamp(texel_sample + corners[i], ivec2(0), textureSize(depth_tx, 0).xy);
    float depth = texelFetch(depth_tx, texel, 0).x;
    if (min_depth > depth) {
      min_depth = depth;
      nearest_texel = texel;
    }
  }

  vec4 vector = velocity_resolve(vector_tx, nearest_texel, min_depth);

  /* Transform to pixel space. */
  vector.xy *= vec2(film_buf.extent);

  return vector.xy;
}

/* \a t is inter-pixel position. 0 means perfectly on a pixel center.
 * Returns weights in both dimensions.
 * Multiply each dimension weights to get final pixel weights. */
void film_get_catmull_rom_weights(vec2 t, out vec2 weights[4])
{
  vec2 t2 = t * t;
  vec2 t3 = t2 * t;
  float fc = 0.5; /* Catmull-Rom. */

  vec2 fct = t * fc;
  vec2 fct2 = t2 * fc;
  vec2 fct3 = t3 * fc;
  weights[0] = (fct2 * 2.0 - fct3) - fct;
  weights[1] = (t3 * 2.0 - fct3) + (-t2 * 3.0 + fct2) + 1.0;
  weights[2] = (-t3 * 2.0 + fct3) + (t2 * 3.0 - (2.0 * fct2)) + fct;
  weights[3] = fct3 - fct2;
}

/* Load color using a special filter to avoid loosing detail.
 * \a texel is sample position with subpixel accuracy. */
vec4 film_sample_catmull_rom(sampler2D color_tx, vec2 input_texel)
{
  vec2 center_texel;
  vec2 inter_texel = modf(input_texel, center_texel);
  vec2 weights[4];
  film_get_catmull_rom_weights(inter_texel, weights);

#if 0 /* Reference. 16 Taps. */
  vec4 color = vec4(0.0);
  for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 4; x++) {
      ivec2 texel = ivec2(center_texel) + ivec2(x, y) - 1;
      texel = clamp(texel, ivec2(0), textureSize(color_tx, 0).xy - 1);
      color += texelFetch(color_tx, texel, 0) * weights[x].x * weights[y].y;
    }
  }
  return color;

#elif 1 /* Optimize version. 5 Bilinear Taps. */
  /**
   * Use optimized version by leveraging bilinear filtering from hardware sampler and by removing
   * corner taps.
   * From "Filmic SMAA" by Jorge Jimenez at Siggraph 2016
   * http://advances.realtimerendering.com/s2016/Filmic%20SMAA%20v7.pptx
   */
  center_texel += 0.5;

  /* Slide 92. */
  vec2 weight_12 = weights[1] + weights[2];
  vec2 uv_12 = (center_texel + weights[2] / weight_12) * film_buf.extent_inv;
  vec2 uv_0 = (center_texel - 1.0) * film_buf.extent_inv;
  vec2 uv_3 = (center_texel + 2.0) * film_buf.extent_inv;

  vec4 color;
  vec4 weight_cross = weight_12.xyyx * vec4(weights[0].yx, weights[3].xy);
  float weight_center = weight_12.x * weight_12.y;

  color = textureLod(color_tx, uv_12, 0.0) * weight_center;
  color += textureLod(color_tx, vec2(uv_12.x, uv_0.y), 0.0) * weight_cross.x;
  color += textureLod(color_tx, vec2(uv_0.x, uv_12.y), 0.0) * weight_cross.y;
  color += textureLod(color_tx, vec2(uv_3.x, uv_12.y), 0.0) * weight_cross.z;
  color += textureLod(color_tx, vec2(uv_12.x, uv_3.y), 0.0) * weight_cross.w;
  /* Re-normalize for the removed corners. */
  return color / (weight_center + sum(weight_cross));

#else /* Nearest interpolation for debugging. 1 Tap. */
  ivec2 texel = ivec2(center_texel) + ivec2(greaterThan(inter_texel, vec2(0.5)));
  texel = clamp(texel, ivec2(0), textureSize(color_tx, 0).xy - 1);
  return texelFetch(color_tx, texel, 0);
#endif
}

/* Return history clipping bounding box in YCoCg color space. */
void film_combined_neighbor_boundbox(ivec2 texel, out vec4 min_c, out vec4 max_c)
{
  /* Plus (+) shape offsets. */
  const ivec2 plus_offsets[5] = ivec2[5](ivec2(0, 0), /* Center */
                                         ivec2(-1, 0),
                                         ivec2(0, -1),
                                         ivec2(1, 0),
                                         ivec2(0, 1));
#if 0
  /**
   * Compute Variance of neighborhood as described in:
   * "An Excursion in Temporal Supersampling" by Marco Salvi at GDC 2016.
   * and:
   * "A Survey of Temporal Antialiasing Techniques" by Yang et al.
   */

  /* First 2 moments. */
  vec4 mu1 = vec4(0), mu2 = vec4(0);
  for (int i = 0; i < 5; i++) {
    vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + plus_offsets[i]);
    mu1 += color;
    mu2 += sqr(color);
  }
  mu1 *= (1.0 / 5.0);
  mu2 *= (1.0 / 5.0);

  /* Extent scaling. Range [0.75..1.25].
   * Balance between more flickering (0.75) or more ghosting (1.25). */
  const float gamma = 1.25;
  /* Standard deviation. */
  vec4 sigma = sqrt(abs(mu2 - sqr(mu1)));
  /* eq. 6 in "A Survey of Temporal Antialiasing Techniques". */
  min_c = mu1 - gamma * sigma;
  max_c = mu1 + gamma * sigma;
#else
  /**
   * Simple bounding box calculation in YCoCg as described in:
   * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014
   */
  min_c = vec4(1e16);
  max_c = vec4(-1e16);
  for (int i = 0; i < 5; i++) {
    vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + plus_offsets[i]);
    min_c = min(min_c, color);
    max_c = max(max_c, color);
  }
  /* (Slide 32) Simple clamp to min/max of 8 neighbors results in 3x3 box artifacts.
   * Round bbox shape by averaging 2 different min/max from 2 different neighborhood. */
  vec4 min_c_3x3 = min_c;
  vec4 max_c_3x3 = max_c;
  const ivec2 corners[4] = ivec2[4](ivec2(-1, -1), ivec2(1, -1), ivec2(-1, 1), ivec2(1, 1));
  for (int i = 0; i < 4; i++) {
    vec4 color = film_texelfetch_as_YCoCg_opacity(combined_tx, texel + corners[i]);
    min_c_3x3 = min(min_c_3x3, color);
    max_c_3x3 = max(max_c_3x3, color);
  }
  min_c = (min_c + min_c_3x3) * 0.5;
  max_c = (max_c + max_c_3x3) * 0.5;
#endif
}

/* 1D equivalent of line_aabb_clipping_dist(). */
float film_aabb_clipping_dist_alpha(float origin, float direction, float aabb_min, float aabb_max)
{
  if (abs(direction) < 1e-5) {
    return 0.0;
  }
  float nearest_plane = (direction > 0.0) ? aabb_min : aabb_max;
  return (nearest_plane - origin) / direction;
}

/* Modulate the history color to avoid ghosting artifact. */
vec4 film_amend_combined_history(
    vec4 min_color, vec4 max_color, vec4 color_history, vec4 src_color, ivec2 src_texel)
{
  /* Clip instead of clamping to avoid color accumulating in the AABB corners. */
  vec4 clip_dir = src_color - color_history;

  float t = line_aabb_clipping_dist(color_history.rgb, clip_dir.rgb, min_color.rgb, max_color.rgb);
  color_history.rgb += clip_dir.rgb * saturate(t);

  /* Clip alpha on its own to avoid interference with other chanels. */
  float t_a = film_aabb_clipping_dist_alpha(color_history.a, clip_dir.a, min_color.a, max_color.a);
  color_history.a += clip_dir.a * saturate(t_a);

  return color_history;
}

float film_history_blend_factor(float velocity,
                                vec2 texel,
                                float luma_min,
                                float luma_max,
                                float luma_incoming,
                                float luma_history)
{
  /* 5% of incoming color by default. */
  float blend = 0.05;
  /* Blend less history if the pixel has substential velocity. */
  blend = mix(blend, 0.20, saturate(velocity * 0.02));
  /**
   * "High Quality Temporal Supersampling" by Brian Karis at Siggraph 2014 (Slide 43)
   * Bias towards history if incomming pixel is near clamping. Reduces flicker.
   */
  float distance_to_luma_clip = min_v2(vec2(luma_history - luma_min, luma_max - luma_history));
  /* Divide by bbox size to get a factor. 2 factor to compensate the line above. */
  distance_to_luma_clip *= 2.0 * safe_rcp(luma_max - luma_min);
  /* Linearly blend when history gets bellow to 25% of the bbox size. */
  blend *= saturate(distance_to_luma_clip * 4.0 + 0.1);
  /* Discard out of view history. */
  if (any(lessThan(texel, vec2(0))) || any(greaterThanEqual(texel, film_buf.extent))) {
    blend = 1.0;
  }
  /* Discard history if invalid. */
  if (film_buf.use_history == false) {
    blend = 1.0;
  }
  return blend;
}

/* Returns resolved final color. */
void film_store_combined(
    FilmSample dst, ivec2 src_texel, vec4 color, float color_weight, inout vec4 display)
{
  if (film_buf.combined_id == -1) {
    return;
  }

  vec4 color_src, color_dst;
  float weight_src, weight_dst;

  /* Undo the weighting to get final spatialy-filtered color. */
  color_src = color / color_weight;

  if (film_buf.use_reprojection) {
    /* Interactive accumulation. Do reprojection and Temporal Anti-Aliasing. */

    /* Reproject by finding where this pixel was in the previous frame. */
    vec2 motion = film_pixel_history_motion_vector(src_texel);
    vec2 history_texel = vec2(dst.texel) + motion;

    float velocity = length(motion);

    /* Load weight if it is not uniform accross the whole buffer (i.e: upsampling, panoramic). */
    // dst.weight = film_weight_load(texel_combined);

    color_dst = film_sample_catmull_rom(in_combined_tx, history_texel);
    color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb);

    /* Get local color bounding box of source neighboorhood. */
    vec4 min_color, max_color;
    film_combined_neighbor_boundbox(src_texel, min_color, max_color);

    float blend = film_history_blend_factor(
        velocity, history_texel, min_color.x, max_color.x, color_src.x, color_dst.x);

    color_dst = film_amend_combined_history(min_color, max_color, color_dst, color_src, src_texel);

    /* Luma weighted blend to avoid flickering. */
    weight_dst = film_luma_weight(color_dst.x) * (1.0 - blend);
    weight_src = film_luma_weight(color_src.x) * (blend);
  }
  else {
    /* Everything is static. Use render accumulation. */
    color_dst = texelFetch(in_combined_tx, dst.texel, 0);
    color_dst.rgb = film_YCoCg_from_scene_linear(color_dst.rgb);

    /* Luma weighted blend to avoid flickering. */
    weight_dst = film_luma_weight(color_dst.x) * dst.weight;
    weight_src = color_weight;
  }
  /* Weighted blend. */
  color = color_dst * weight_dst + color_src * weight_src;
  color /= weight_src + weight_dst;

  color.rgb = film_scene_linear_from_YCoCg(color.rgb);

  /* Fix alpha not accumulating to 1 because of float imprecision. */
  if (color.a > 0.995) {
    color.a = 1.0;
  }

  /* Filter NaNs. */
  if (any(isnan(color))) {
    color = vec4(0.0, 0.0, 0.0, 1.0);
  }

  if (film_buf.display_id == -1) {
    display = color;
  }
  imageStore(out_combined_img, dst.texel, color);
}

void film_store_color(FilmSample dst, int pass_id, vec4 color, inout vec4 display)
{
  if (pass_id == -1) {
    return;
  }

  vec4 data_film = imageLoad(color_accum_img, ivec3(dst.texel, pass_id));

  color = (data_film * dst.weight + color) * dst.weight_sum_inv;

  /* Filter NaNs. */
  if (any(isnan(color))) {
    color = vec4(0.0, 0.0, 0.0, 1.0);
  }

  if (film_buf.display_id == pass_id) {
    display = color;
  }
  imageStore(color_accum_img, ivec3(dst.texel, pass_id), color);
}

void film_store_value(FilmSample dst, int pass_id, float value, inout vec4 display)
{
  if (pass_id == -1) {
    return;
  }

  float data_film = imageLoad(value_accum_img, ivec3(dst.texel, pass_id)).x;

  value = (data_film * dst.weight + value) * dst.weight_sum_inv;

  /* Filter NaNs. */
  if (isnan(value)) {
    value = 0.0;
  }

  if (film_buf.display_id == pass_id) {
    display = vec4(value, value, value, 1.0);
  }
  imageStore(value_accum_img, ivec3(dst.texel, pass_id), vec4(value));
}

/* Nearest sample variant. Always stores the data. */
void film_store_data(ivec2 texel_film, int pass_id, vec4 data_sample, inout vec4 display)
{
  if (pass_id == -1) {
    return;
  }

  if (film_buf.display_id == pass_id) {
    display = data_sample;
  }
  imageStore(color_accum_img, ivec3(texel_film, pass_id), data_sample);
}

void film_store_depth(ivec2 texel_film, float value, out float out_depth)
{
  if (film_buf.depth_id == -1) {
    return;
  }

  out_depth = film_depth_convert_to_scene(value);

  imageStore(depth_img, texel_film, vec4(out_depth));
}

void film_store_distance(ivec2 texel, float value)
{
  imageStore(out_weight_img, ivec3(texel, WEIGHT_lAYER_DISTANCE), vec4(value));
}

void film_store_weight(ivec2 texel, float value)
{
  imageStore(out_weight_img, ivec3(texel, WEIGHT_lAYER_ACCUMULATION), vec4(value));
}

float film_display_depth_ammend(ivec2 texel, float depth)
{
  /* This effectively offsets the depth of the whole 2x2 region to the lowest value of the region
   * twice. One for X and one for Y direction. */
  /* TODO(fclem): This could be improved as it gives flickering result at depth discontinuity.
   * But this is the quickest stable result I could come with for now. */
#ifdef GPU_FRAGMENT_SHADER
  depth += fwidth(depth);
#endif
  /* Small offset to avoid depth test lessEqual failing because of all the conversions loss. */
  depth += 2.4e-7 * 4.0;
  return saturate(depth);
}

/** \} */

/** NOTE: out_depth is scene linear depth from the camera origin. */
void film_process_data(ivec2 texel_film, out vec4 out_color, out float out_depth)
{
  out_color = vec4(0.0);
  out_depth = 0.0;

  float weight_accum = film_weight_accumulation(texel_film);
  float film_weight = film_weight_load(texel_film);
  float weight_sum = film_weight + weight_accum;
  film_store_weight(texel_film, weight_sum);

  FilmSample dst;
  dst.texel = texel_film;
  dst.weight = film_weight;
  dst.weight_sum_inv = 1.0 / weight_sum;

  /* NOTE: We split the accumulations into separate loops to avoid using too much registers and
   * maximize occupancy. */

  if (film_buf.combined_id != -1) {
    /* NOTE: Do weight accumulation again since we use custom weights. */
    float weight_accum = 0.0;
    vec4 combined_accum = vec4(0.0);

    FilmSample src;
    for (int i = film_buf.samples_len - 1; i >= 0; i--) {
      src = film_sample_get(i, texel_film);
      film_sample_accum_combined(src, combined_accum, weight_accum);
    }
    /* NOTE: src.texel is center texel in incomming data buffer. */
    film_store_combined(dst, src.texel, combined_accum, weight_accum, out_color);
  }

  if (film_buf.has_data) {
    float film_distance = film_distance_load(texel_film);

    /* Get sample closest to target texel. It is always sample 0. */
    FilmSample film_sample = film_sample_get(0, texel_film);

    if (film_buf.use_reprojection || film_sample.weight < film_distance) {
      vec4 normal = texelFetch(normal_tx, film_sample.texel, 0);
      float depth = texelFetch(depth_tx, film_sample.texel, 0).x;
      vec4 vector = velocity_resolve(vector_tx, film_sample.texel, depth);

      film_store_depth(texel_film, depth, out_depth);
      film_store_data(texel_film, film_buf.normal_id, normal, out_color);
      film_store_data(texel_film, film_buf.vector_id, vector, out_color);
      film_store_distance(texel_film, film_sample.weight);
    }
    else {
      out_depth = imageLoad(depth_img, texel_film).r;
    }
  }

  if (film_buf.any_render_pass_1) {
    vec4 diffuse_light_accum = vec4(0.0);
    vec4 specular_light_accum = vec4(0.0);
    vec4 volume_light_accum = vec4(0.0);
    vec4 emission_accum = vec4(0.0);

    for (int i = 0; i < film_buf.samples_len; i++) {
      FilmSample src = film_sample_get(i, texel_film);
      film_sample_accum(src, film_buf.diffuse_light_id, diffuse_light_tx, diffuse_light_accum);
      film_sample_accum(src, film_buf.specular_light_id, specular_light_tx, specular_light_accum);
      film_sample_accum(src, film_buf.volume_light_id, volume_light_tx, volume_light_accum);
      film_sample_accum(src, film_buf.emission_id, emission_tx, emission_accum);
    }
    film_store_color(dst, film_buf.diffuse_light_id, diffuse_light_accum, out_color);
    film_store_color(dst, film_buf.specular_light_id, specular_light_accum, out_color);
    film_store_color(dst, film_buf.volume_light_id, volume_light_accum, out_color);
    film_store_color(dst, film_buf.emission_id, emission_accum, out_color);
  }

  if (film_buf.any_render_pass_2) {
    vec4 diffuse_color_accum = vec4(0.0);
    vec4 specular_color_accum = vec4(0.0);
    vec4 environment_accum = vec4(0.0);
    float mist_accum = 0.0;
    float shadow_accum = 0.0;
    float ao_accum = 0.0;

    for (int i = 0; i < film_buf.samples_len; i++) {
      FilmSample src = film_sample_get(i, texel_film);
      film_sample_accum(src, film_buf.diffuse_color_id, diffuse_color_tx, diffuse_color_accum);
      film_sample_accum(src, film_buf.specular_color_id, specular_color_tx, specular_color_accum);
      film_sample_accum(src, film_buf.environment_id, environment_tx, environment_accum);
      film_sample_accum(src, film_buf.shadow_id, shadow_tx, shadow_accum);
      film_sample_accum(src, film_buf.ambient_occlusion_id, ambient_occlusion_tx, ao_accum);
      film_sample_accum_mist(src, mist_accum);
    }
    film_store_color(dst, film_buf.diffuse_color_id, diffuse_color_accum, out_color);
    film_store_color(dst, film_buf.specular_color_id, specular_color_accum, out_color);
    film_store_color(dst, film_buf.environment_id, environment_accum, out_color);
    film_store_value(dst, film_buf.shadow_id, shadow_accum, out_color);
    film_store_value(dst, film_buf.ambient_occlusion_id, ao_accum, out_color);
    film_store_value(dst, film_buf.mist_id, mist_accum, out_color);
  }

  for (int aov = 0; aov < film_buf.aov_color_len; aov++) {
    vec4 aov_accum = vec4(0.0);

    for (int i = 0; i < film_buf.samples_len; i++) {
      FilmSample src = film_sample_get(i, texel_film);
      film_sample_accum(src, aov, aov_color_tx, aov_accum);
    }
    film_store_color(dst, film_buf.aov_color_id + aov, aov_accum, out_color);
  }

  for (int aov = 0; aov < film_buf.aov_value_len; aov++) {
    float aov_accum = 0.0;

    for (int i = 0; i < film_buf.samples_len; i++) {
      FilmSample src = film_sample_get(i, texel_film);
      film_sample_accum(src, aov, aov_value_tx, aov_accum);
    }
    film_store_value(dst, film_buf.aov_value_id + aov, aov_accum, out_color);
  }
}