Quake II RTX doxygen  1.0 dev
tone_mapping.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 2019, NVIDIA CORPORATION. All rights reserved.
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18 
19 // ========================================================================= //
20 //
21 // This is the CPU-side code for the tone mapper, which is based on part of
22 // Eilertsen, Mantiuk, and Unger's paper *Real-time noise-aware tone mapping*,
23 // with some additional modifications that we found useful.
24 //
25 // This file shows how the tone mapping pipeline is created and updated, as
26 // well as part of how the tone mapper can be controlled by the application
27 // and by CVARs. (For a CVAR reference, see `global_ubo.h`, and in particular,
28 // those CVARS starting with `tm_`, for `tonemapper_`.)
29 //
30 // The tone mapper consists of three compute shaders, a utilities file, and
31 // a CPU-side code file. For an overview of the tone mapper, see
32 // `tone_mapping_histogram.comp`.
33 //
34 // Here are the functions in this file:
35 // VkResult vkpt_tone_mapping_initialize() - creates our pipeline layouts
36 //
37 // VkResult vkpt_tone_mapping_destroy() - destroys our pipeline layouts
38 //
39 // void vkpt_tone_mapping_request_reset() - tells the tone mapper to calculate
40 // the next tone curve without blending with previous frames' tone curves
41 //
42 // VkResult vkpt_tone_mapping_create_pipelines() - creates our pipelines
43 //
44 // VkResult vkpt_tone_mapping_reset(VkCommandBuffer cmd_buf) - adds commands
45 // to the command buffer to clear the histogram image
46 //
47 // VkResult vkpt_tone_mapping_destroy_pipelines() - destroys our pipelines
48 //
49 // VkResult vkpt_tone_mapping_record_cmd_buffer(VkCommandBuffer cmd_buf,
50 // float frame_time) - records the commands to apply tone mapping to the
51 // VKPT_IMG_TAA_OUTPUT image in-place, given the time between this frame and
52 // the previous frame.
53 //
54 // ========================================================================= //
55 
56 #include "vkpt.h"
57 
58 // Here are each of the pipelines we'll be using, followed by an additional
59 // enum value to count the number of tone mapping pipelines.
60 enum {
65 };
66 
67 static VkPipeline pipelines[TM_NUM_PIPELINES];
69 static VkPipelineLayout pipeline_layout_tone_mapping_curve;
70 static VkPipelineLayout pipeline_layout_tone_mapping_apply;
71 static int reset_required = 1; // If 1, recomputes tone curve based only on this frame
72 
73 
74 // Creates our pipeline layouts.
75 VkResult
77 {
78  VkDescriptorSetLayout desc_set_layouts[] = {
82  };
83 
84  VkPushConstantRange push_constant_range_curve = {
85  .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
86  .offset = 0,
87  .size = 16*sizeof(float)
88  };
89 
90  VkPushConstantRange push_constant_range_apply = {
91  .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
92  .offset = 0,
93  .size = 3*sizeof(float)
94  };
95 
97  .setLayoutCount = LENGTH(desc_set_layouts),
98  .pSetLayouts = desc_set_layouts,
99  .pushConstantRangeCount = 0,
100  .pPushConstantRanges = NULL
101  );
103 
105  .setLayoutCount = LENGTH(desc_set_layouts),
106  .pSetLayouts = desc_set_layouts,
107  .pushConstantRangeCount = 1,
108  .pPushConstantRanges = &push_constant_range_curve
109  );
111 
113  .setLayoutCount = LENGTH(desc_set_layouts),
114  .pSetLayouts = desc_set_layouts,
115  .pushConstantRangeCount = 1,
116  .pPushConstantRanges = &push_constant_range_apply
117  );
119 
120  return VK_SUCCESS;
121 }
122 
123 
124 // Destroys our pipeline layouts.
125 VkResult
127 {
128  vkDestroyPipelineLayout(qvk.device, pipeline_layout_tone_mapping_histogram, NULL);
129  vkDestroyPipelineLayout(qvk.device, pipeline_layout_tone_mapping_curve, NULL);
130  vkDestroyPipelineLayout(qvk.device, pipeline_layout_tone_mapping_apply, NULL);
131  return VK_SUCCESS;
132 }
133 
134 
135 // Tells the tone mapper to calculate the next tone curve without blending with
136 // previous frames' tone curves.
137 void
139 {
140  reset_required = 1;
141 }
142 
143 
144 // Creates our pipelines.
145 VkResult
147 {
148  VkComputePipelineCreateInfo pipeline_info[TM_NUM_PIPELINES] = {
150  .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
151  .stage = SHADER_STAGE(QVK_MOD_TONE_MAPPING_HISTOGRAM_COMP, VK_SHADER_STAGE_COMPUTE_BIT),
153  },
154  [TONE_MAPPING_CURVE] = {
155  .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
156  .stage = SHADER_STAGE(QVK_MOD_TONE_MAPPING_CURVE_COMP, VK_SHADER_STAGE_COMPUTE_BIT),
158  },
159  [TONE_MAPPING_APPLY] = {
160  .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
161  .stage = SHADER_STAGE(QVK_MOD_TONE_MAPPING_APPLY_COMP, VK_SHADER_STAGE_COMPUTE_BIT),
163  },
164  };
165 
166  _VK(vkCreateComputePipelines(qvk.device, 0, LENGTH(pipeline_info), pipeline_info, 0, pipelines));
167 
168  reset_required = 1;
169 
170  return VK_SUCCESS;
171 }
172 
173 
174 // Adds commands to the command buffer to clear the histogram image.
175 VkResult
176 vkpt_tone_mapping_reset(VkCommandBuffer cmd_buf)
177 {
178  VkClearColorValue clear_histogram = {
179  .uint32 = { 0, 0, 0, 0 }
180  };
181 
182  BUFFER_BARRIER(cmd_buf,
183  .buffer = qvk.buf_tonemap.buffer,
184  .offset = 0,
185  .size = VK_WHOLE_SIZE,
186  .srcAccessMask = 0,
187  .dstAccessMask = 0
188  );
189 
190  vkCmdFillBuffer(cmd_buf, qvk.buf_tonemap.buffer,
191  0, VK_WHOLE_SIZE, 0);
192 
193  BUFFER_BARRIER(cmd_buf,
194  .buffer = qvk.buf_tonemap.buffer,
195  .offset = 0,
196  .size = VK_WHOLE_SIZE,
197  .srcAccessMask = 0,
198  .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT
199  );
200 
201  return VK_SUCCESS;
202 }
203 
204 
205 // Destroys our pipelines.
206 VkResult
208 {
209  for (int i = 0; i < TM_NUM_PIPELINES; i++)
210  vkDestroyPipeline(qvk.device, pipelines[i], NULL);
211  return VK_SUCCESS;
212 }
213 
214 
215 // Shorthand to record a resource barrier on a single image, to prevent threads
216 // from trying to read from this image before it has been written to by the
217 // previous stage.
218 #define BARRIER_COMPUTE(cmd_buf, img) \
219  do { \
220  VkImageSubresourceRange subresource_range = { \
221  .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \
222  .baseMipLevel = 0, \
223  .levelCount = 1, \
224  .baseArrayLayer = 0, \
225  .layerCount = 1 \
226  }; \
227  IMAGE_BARRIER(cmd_buf, \
228  .image = img, \
229  .subresourceRange = subresource_range, \
230  .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, \
231  .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, \
232  .oldLayout = VK_IMAGE_LAYOUT_GENERAL, \
233  .newLayout = VK_IMAGE_LAYOUT_GENERAL, \
234  ); \
235  } while(0)
236 
237 
238 // Records the commands to apply tone mapping to the VKPT_IMG_TAA_OUTPUT image
239 // in-place, given the time between this frame and the previous frame.
240 VkResult
241 vkpt_tone_mapping_record_cmd_buffer(VkCommandBuffer cmd_buf, float frame_time)
242 {
243  if (reset_required)
244  {
245  // Clear the histogram image.
246  vkpt_tone_mapping_reset(cmd_buf);
247  }
248 
249  VkDescriptorSet desc_sets[] = {
253  };
254  BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_TAA_OUTPUT]);
255 
256 
257  // Record instructions to run the compute shader that updates the histogram.
258  vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipelines[TONE_MAPPING_HISTOGRAM]);
259  vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE,
260  pipeline_layout_tone_mapping_histogram, 0, LENGTH(desc_sets), desc_sets, 0, 0);
261 
262  vkCmdDispatch(cmd_buf,
263  (qvk.extent_render.width + 15) / 16,
264  (qvk.extent_render.height + 15) / 16,
265  1);
266 
267  BUFFER_BARRIER(cmd_buf,
268  .buffer = qvk.buf_tonemap.buffer,
269  .offset = 0,
270  .size = VK_WHOLE_SIZE,
271  .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
272  .dstAccessMask = VK_ACCESS_SHADER_READ_BIT
273  );
274 
275 
276  // Record instructions to run the compute shader that computes the tone
277  // curve and autoexposure constants from the histogram.
278  vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipelines[TONE_MAPPING_CURVE]);
279  vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE,
280  pipeline_layout_tone_mapping_curve, 0, LENGTH(desc_sets), desc_sets, 0, 0);
281 
282  // Compute the push constants for the tone curve generation shader:
283  // whether to ignore previous tone curve results, how much to blend this
284  // frame's tone curve with the previous frame's tone curve, and the kernel
285  // used to filter the tone curve's slopes.
286  // This is one of the things we added to Eilertsen's tone mapper, and helps
287  // prevent artifacts that occur when the tone curve's slopes become flat or
288  // change too suddenly (much like when you specify a curve in an image
289  // processing application that is too intense). This especially helps on
290  // shadow edges in some scenes.
291  // In addition, we assume the kernel is symmetric; this allows us to only
292  // specify half of it in our push constant buffer.
293 
294  // Note that the second argument of Cvar_Get only specifies the default
295  // value in code if none is set; the value of tm_slope_blur_sigma specified
296  // in global_ubo.h will override this.
297  float slope_blur_sigma = Cvar_Get("tm_slope_blur_sigma", "6.0", 0)->value;
298  float push_constants_tm2_curve[16] = {
299  reset_required ? 1.0 : 0.0, // 1 means reset the histogram
300  frame_time, // Frame time
301  0.0, 0.0, 0.0, 0.0, // Slope kernel filter
302  0.0, 0.0, 0.0, 0.0,
303  0.0, 0.0, 0.0, 0.0,
304  0.0, 0.0
305  };
306 
307  // Compute Gaussian curve and sum, taking symmetry into account.
308  float gaussian_sum = 0.0;
309  for (int i = 0; i < 14; ++i)
310  {
311  float kernel_value = exp(-i * i / (2.0 * slope_blur_sigma * slope_blur_sigma));
312  gaussian_sum += kernel_value * (i == 0 ? 1 : 2);
313  push_constants_tm2_curve[i + 2] = kernel_value;
314  }
315  // Normalize the result (since even with an analytic normalization factor,
316  // the results may not sum to one).
317  for (int i = 0; i < 14; ++i) {
318  push_constants_tm2_curve[i + 2] /= gaussian_sum;
319  }
320 
321  vkCmdPushConstants(cmd_buf, pipeline_layout_tone_mapping_curve,
322  VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants_tm2_curve), push_constants_tm2_curve);
323 
324  vkCmdDispatch(cmd_buf, 1, 1, 1);
325 
326  BUFFER_BARRIER(cmd_buf,
327  .buffer = qvk.buf_tonemap.buffer,
328  .offset = 0,
329  .size = VK_WHOLE_SIZE,
330  .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
331  .dstAccessMask = VK_ACCESS_SHADER_READ_BIT
332  );
333 
334 
335  // Record instructions to apply our tone curve to the final image, apply
336  // the autoexposure tone mapper to the final image, and blend the results
337  // of the two techniques.
338  vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE, pipelines[TONE_MAPPING_APPLY]);
339  vkCmdBindDescriptorSets(cmd_buf, VK_PIPELINE_BIND_POINT_COMPUTE,
340  pipeline_layout_tone_mapping_apply, 0, LENGTH(desc_sets), desc_sets, 0, 0);
341 
342  // At the end of the hue-preserving tone mapper, the luminance of every
343  // pixel is mapped to the range [0,1]. However, because this tone
344  // mapper adjusts luminance while preserving hue and saturation, the values
345  // of some RGB channels may lie outside [0,1]. To finish off the tone
346  // mapping pipeline and since we want the brightest colors in the scene to
347  // be desaturated a bit for display, we apply a subtle user-configurable
348  // Reinhard tone mapping curve to the brighest values in the image at this
349  // point, preserving pixels with luminance below tm_knee_start.
350  //
351  // If we wanted to support an arbitrary SDR color grading pipeline here or
352  // implement an additional filmic tone mapping pass, for instance, this is
353  // roughly around where it would be applied. For applications that need to
354  // output both SDR and HDR images but for which creating custom grades
355  // for each format is impractical, one common approach is to
356  // (roughly) use the HDR->SDR transformation to map an SDR color grading
357  // function back to an HDR color grading function.
358 
359  // Defines the white point and where we switch from an identity transform
360  // to a Reinhard transform in the additional tone mapper we apply at the
361  // end of the previous tone mapping pipeline.
362  // Must be between 0 and 1; pixels with luminances above this value have
363  // their RGB values slowly clamped to 1, up to tm_white_point.
364  float knee_start = Cvar_Get("tm_knee_start", "0.9", 0)->value;
365  // Should be greater than 1; defines those RGB values that get mapped to 1.
366  float knee_white_point = Cvar_Get("tm_white_point", "10.0", 0)->value;
367 
368  // We modify Reinhard to smoothly blend with the identity transform up to tm_knee_start.
369  // We need to find w, a, and b such that in y(x) = (wx+a)/(x+b),
370  // * y(knee_start) = tm_knee_start
371  // * dy/dx(knee_start) = 1
372  // * y(knee_white_point) = tm_white_point.
373  // The solution is as follows:
374  float knee_w = (knee_start*(knee_start - 2.0) + knee_white_point) / (knee_white_point - 1.0);
375  float knee_a = -knee_start * knee_start;
376  float knee_b = knee_w - 2.0*knee_start;
377 
378  float push_constants_tm2_apply[3] = {
379  knee_w, // knee_w in piecewise knee adjustment
380  knee_a, // knee_a in piecewise knee adjustment
381  knee_b, // knee_b in piecewise knee adjustment
382  };
383 
384  vkCmdPushConstants(cmd_buf, pipeline_layout_tone_mapping_apply,
385  VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push_constants_tm2_apply), push_constants_tm2_apply);
386 
387  vkCmdDispatch(cmd_buf,
388  (qvk.extent_render.width + 15) / 16,
389  (qvk.extent_render.height + 15) / 16,
390  1);
391 
392  // Because VKPT_IMG_TAA_OUTPUT changed, we make sure to wait for the image
393  // to be written before continuing. This could be ensured in several
394  // other ways as well.
395  BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_TAA_OUTPUT]);
396 
397  reset_required = 0;
398 
399  return VK_SUCCESS;
400 }
QVK_s::desc_set_ubo
VkDescriptorSet desc_set_ubo
Definition: vkpt.h:226
BUFFER_BARRIER
#define BUFFER_BARRIER(cmd_buf,...)
Definition: vk_util.h:68
TONE_MAPPING_APPLY
@ TONE_MAPPING_APPLY
Definition: tone_mapping.c:63
CREATE_PIPELINE_LAYOUT
#define CREATE_PIPELINE_LAYOUT(dev, layout,...)
Definition: vk_util.h:82
QVK_s::buf_tonemap
BufferResource_t buf_tonemap
Definition: vkpt.h:252
QVK_s::device
VkDevice device
Definition: vkpt.h:172
pipeline_layout_tone_mapping_histogram
static VkPipelineLayout pipeline_layout_tone_mapping_histogram
Definition: tone_mapping.c:68
TONE_MAPPING_CURVE
@ TONE_MAPPING_CURVE
Definition: tone_mapping.c:62
Cvar_Get
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags)
Definition: cvar.c:257
vkpt.h
pipelines
static VkPipeline pipelines[TM_NUM_PIPELINES]
Definition: tone_mapping.c:67
vkpt_tone_mapping_create_pipelines
VkResult vkpt_tone_mapping_create_pipelines()
Definition: tone_mapping.c:146
_VK
#define _VK(...)
Definition: vkpt.h:65
reset_required
static int reset_required
Definition: tone_mapping.c:71
QVK_s::desc_set_vertex_buffer
VkDescriptorSet desc_set_vertex_buffer
Definition: vkpt.h:241
vkpt_tone_mapping_initialize
VkResult vkpt_tone_mapping_initialize()
Definition: tone_mapping.c:76
pipeline_layout_tone_mapping_curve
static VkPipelineLayout pipeline_layout_tone_mapping_curve
Definition: tone_mapping.c:69
BufferResource_s::buffer
VkBuffer buffer
Definition: vk_util.h:34
QVK_s::images
VkImage images[NUM_VKPT_IMAGES]
Definition: vkpt.h:231
LENGTH
#define LENGTH(a)
Definition: tent.c:228
qvk
QVK_t qvk
Definition: main.c:377
vkpt_tone_mapping_destroy
VkResult vkpt_tone_mapping_destroy()
Definition: tone_mapping.c:126
TONE_MAPPING_HISTOGRAM
@ TONE_MAPPING_HISTOGRAM
Definition: tone_mapping.c:61
SHADER_STAGE
#define SHADER_STAGE(_module, _stage)
Definition: vkpt.h:116
vkpt_tone_mapping_record_cmd_buffer
VkResult vkpt_tone_mapping_record_cmd_buffer(VkCommandBuffer cmd_buf, float frame_time)
Definition: tone_mapping.c:241
QVK_s::extent_render
VkExtent2D extent_render
Definition: vkpt.h:184
vkpt_tone_mapping_request_reset
void vkpt_tone_mapping_request_reset()
Definition: tone_mapping.c:138
TM_NUM_PIPELINES
@ TM_NUM_PIPELINES
Definition: tone_mapping.c:64
vkpt_tone_mapping_destroy_pipelines
VkResult vkpt_tone_mapping_destroy_pipelines()
Definition: tone_mapping.c:207
vkpt_tone_mapping_reset
VkResult vkpt_tone_mapping_reset(VkCommandBuffer cmd_buf)
Definition: tone_mapping.c:176
ATTACH_LABEL_VARIABLE
#define ATTACH_LABEL_VARIABLE(a, type)
Definition: vk_util.h:137
QVK_s::desc_set_layout_textures
VkDescriptorSetLayout desc_set_layout_textures
Definition: vkpt.h:228
QVK_s::desc_set_layout_ubo
VkDescriptorSetLayout desc_set_layout_ubo
Definition: vkpt.h:225
pipeline_layout_tone_mapping_apply
static VkPipelineLayout pipeline_layout_tone_mapping_apply
Definition: tone_mapping.c:70
BARRIER_COMPUTE
#define BARRIER_COMPUTE(cmd_buf, img)
Definition: tone_mapping.c:218
qvk_get_current_desc_set_textures
VkDescriptorSet qvk_get_current_desc_set_textures()
Definition: main.c:1847
QVK_s::desc_set_layout_vertex_buffer
VkDescriptorSetLayout desc_set_layout_vertex_buffer
Definition: vkpt.h:240