File size: 22,274 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
---
--- Defines the post-processing passes to be executed during the rendering pipeline.
--- This function is called during the first load of the application to set up the post-processing pipeline.
--- The post-processing passes are defined using a table of pass configurations, where each pass has a `shader` and a `name` field.
--- The `shader` field specifies the HLSL shader file to be used for the pass, and the `name` field is a unique identifier for the pass.
--- Some passes also have additional configuration options, such as `dispatchX` and `dispatchY` for compute shader passes.
---
--- @function PostProc_DefinePasses
--- @param passes table A table of pass configurations, where each entry is a table with the following fields:
---   - `shader`: the HLSL shader file to be used for the pass
---   - `name`: a unique identifier for the pass
---   - `dispatchX`: the x-dimension of the compute shader dispatch (optional)
---   - `dispatchY`: the y-dimension of the compute shader dispatch (optional)
--- @return none
if FirstLoad then
	PostProc_DefinePasses {
		{ shader = "PostProcCommon.fx" },
		{ name = "down_4x" },
		{ name = "sqgauss" },
		{ name = "sqgauss9" },
		{ name = "sqgauss9_coc" },
		{ name = "coc_down_4x" },
		{ name = "coc_max" },
		{ name = "dof_apply" },
		{ name = "blur_desat" },
		{ name = "radial_blur" },
		{ name = "square_grid" },
		{ name = "grid45" },
		{ name = "hexgrid" },
		{ name = "isolines" }, { name = "isolines2" },
		{ name = "bilinear_scaling" },
		
		{ shader = "PostProcFSR.fx" },
		{ name = "fsr_upscale_fp32", dispatchX = 16, dispatchY = 16 },
		{ name = "fsr_rcas_fp32", dispatchX = 16, dispatchY = 16 },
		
		{ shader = "PostProcUpsample.fx" },
		{ name = "upscale_fmt_unorm_r24_uint_g8" },
		
		{ shader = "PostProcBloom.fx" },
		{ name = "hgauss_tint" },
		{ name = "hgauss_ldr" },
		{ name = "hgauss" },
		{ name = "vgauss_add" },
		{ name = "vgauss_ldr" },
		{ name = "vgauss" },		
		{ name = "bloom_output_raw_auto_exposure_split" },
		{ name = "bloom_output_raw" },
		{ name = "bloom_auto_exposure_split" },
		{ name = "bloom" },		
		{ name = "output_raw_auto_exp_split" },
		{ name = "output_raw" },
		{ name = "compose" },
		{ name = "auto_exp_split" },		
		{ name = "bright_pass_and_down_4x" },
		
		{ shader = "PostProcFXAA.fx" },
		{ name = "fxaa" },
		
		{ shader = "PostProcSMAA.fx" },
		{ name = "edge_detection" },
		{ name = "blending_weight_calc" },
		{ name = "neighborhood_blending" },

		{ shader = "PostProcHeatHaze.fx" },
		{ name = "heat_haze" },

		{ shader = "PostProcDebug.fx" },
		{ name = "debug_chroma_key" },
		{ name = "debug_color_pick" },
		
		{ shader = "PhotoFilter.fx" },
		{ name = "black_and_white_1" },
		{ name = "black_and_white_2" },
		{ name = "black_and_white_3" },
		{ name = "bleach_bypass" },
		{ name = "cover_art" },
		{ name = "orton_effect" },
		
		{ shader = "PostProcContour.fx" },
		{ name = "contour_inner" },
		{ name = "contour_inner_motion_vectors" },

		{ shader = "PostProcDebugMode.fx" },
		{ name = "debug_hue" },
		{ name = "debug_saturation" },
		{ name = "debug_lightness" },
	}
end

---
--- Returns the linear backbuffer format.
---
--- If the supported shader model is not HLSL 5.0, the linear data format of the backbuffer format is returned.
---
--- @return string The linear backbuffer format.
function GetLinearBackbufferFormat()
	local format = GetBackBufferFormat()
	if GetSupportedShaderModel() ~= const.ShaderModelHLSL5_0 then
		format = GetLinearDataFormat(format)
	end 
	return format
end
local function GetLinearBackbufferFormat()
	local format = GetBackBufferFormat()
	if GetSupportedShaderModel() ~= const.ShaderModelHLSL5_0 then
		format = GetLinearDataFormat(format)
	end 
	return format
end

---
--- Rebuilds the post-processing pipeline.
---
--- This function is responsible for setting up the post-processing pipeline based on the current game settings.
--- It creates the necessary render targets, sets up the rendering stages, and configures the post-processing effects.
---
--- The pipeline is rebuilt in a separate real-time thread to avoid stalling the main game thread.
---
function PP_Rebuild()
end
local function PP_RebuildInternal()
	local screen_blur = hr.EnablePostProcScreenBlur > 0
	local auto_exposure_split = hr.EnablePostProcExposureSplit == 1
	local object_marking = hr.EnableObjectMarking > 0
	local contour_inner = hr.EnableContourInner > 0
	local temporal_type = hr.TemporalGetType()
	local temporal = temporal_type== "fsr2" or temporal_type == "dlss" or temporal_type == "xess"
	local fxaa = hr.EnablePostProcAA == 1 and not temporal and not screen_blur
	local smaa = hr.EnablePostProcAA == 2 and not temporal and not screen_blur
	local bloom = hr.EnablePostProcBloom > 0 and not screen_blur
	local dof = hr.EnablePostProcDOF > 0 and not screen_blur
	local distance_blur = hr.EnablePostProcDistanceBlur > 0 and not screen_blur
	local radial_blur = hr.EnablePostProcRadialBlur > 0 and not screen_blur
	local upscaling = hr.ResolutionPercent < 100 and not screen_blur
	local fsr_upscale = hr.ResolutionUpscale == "fsr" and upscaling
	local sharpen = hr.Sharpness > 0.0 and not screen_blur
	local output_raw = hr.PostProcRAWOutputPath ~= ""

	local registers = {}
	local stages = {}

	local regFlags = 0

	if distance_blur then table.iappend( registers, {
		{ name = "blur0", size_div = 4, register_flags = regFlags },
		{ name = "blur1", size_div = 4, register_flags = regFlags }})
	end
	
	if object_marking then
		table.iappend( registers, {
			{ name = "object_marking_depth", size_div = 1, register_flags = regFlags, format = const.fmt_unorm16_c1, resource_flags = const.GFXResourceFlagUsageDSV },
		})
	end
	
	if bloom then table.iappend( registers, {
		{ name = "mip1",  size_div = 2, register_flags = regFlags },
		{ name = "mip2",  size_div = 4, register_flags = regFlags },
		{ name = "mip3",  size_div = 8, register_flags = regFlags },
		{ name = "mip4",  size_div = 16, register_flags = regFlags },
		{ name = "mip5",  size_div = 32, register_flags = regFlags },
		{ name = "mip6",  size_div = 64, register_flags = regFlags },
		
		{ name = "mip2hb",  size_div = 4, register_flags = regFlags },
		{ name = "mip3hb",  size_div = 8, register_flags = regFlags },
		{ name = "mip4hb",  size_div = 16, register_flags = regFlags },
		{ name = "mip5hb",  size_div = 32, register_flags = regFlags },
		{ name = "mip6hb",  size_div = 64, register_flags = regFlags }})
	end
	
	if output_raw then
		table.iappend( registers, {
			{ name = "raw",  size_div = 1, register_flags = regFlags, format = const.fmt_unorm_r10g10b10a2 },
		})
	end
	
	if smaa then
		local SMAA_SearchTex_id = ResourceManager.GetResourceID("CommonAssets/System/SMAA_SearchTex.dds")
		local SMAA_AreaTex_id = ResourceManager.GetResourceID("CommonAssets/System/SMAA_AreaTex.dds")
		table.iappend( registers, {
			{ name = "SMAA_edgesTex", size_div = 1, register_flags = regFlags, format = const.fmt_unorm8_c2 },
			{ name = "SMAA_blendTex", size_div = 1, register_flags = regFlags, format = const.fmt_unorm8_c4 },
			{ name = "SMAA_SearchTex", texture = AsyncGetResource(SMAA_SearchTex_id) },
			{ name = "SMAA_AreaTex", texture = AsyncGetResource(SMAA_AreaTex_id) },
		})
	end

	if dof then table.iappend( registers, {
		{ name="dof_heavy", size_div = 4, register_flags = regFlags },
		{ name="dof_heavy_blurred", size_div = 4, register_flags = regFlags },
		{ name="dof_medium", size_div = 2, register_flags = regFlags },
		{ name="dof_medium_blurred", size_div = 2, register_flags = regFlags },
		{ name="coc_small", size_div = 4, register_flags = regFlags, format = const.fmt_unorm8_c1 },
		{ name="coc_small_blurred", size_div = 4, register_flags = regFlags, format = const.fmt_unorm8_c1 },
		{ name="coc_max", size_div = 4, register_flags = regFlags, format = const.fmt_unorm8_c1 },
	})
	end

	table.iappend( registers, {
		{ name="composed_ldr", size_div = 1, format = GetBackBufferFormat(), resource_flags = const.GFXResourceFlagUsageSRV | const.GFXResourceFlagUsageRTV },
	})

	if upscaling or temporal then 		
		table.iappend( registers, {
			{ name="fsr_upscale", size_div = 1, register_flags = const.rfBackbufferBaseSize, format = GetLinearDataFormat(GetBackBufferFormat()), resource_flags = const.GFXResourceFlagUsageSRV | const.GFXResourceFlagUsageUAV | const.GFXResourceFlagUsageRTV },
		})
	end

	if sharpen then
		table.iappend( registers, {
			{ name="sharpen", size_div = 1, register_flags = const.rfBackbufferBaseSize | const.rfDontClear, format = GetLinearDataFormat(GetBackBufferFormat()), resource_flags = const.GFXResourceFlagUsageSRV | const.GFXResourceFlagUsageUAV | const.GFXResourceFlagUsageRTV },
		})
	end
	
	if contour_inner then
		table.iappend( registers, {
			{ name="contour_inner_depth", size_div = 1, register_flags = const.rfBackbufferBaseSize, format = const.fmt_float32_c1, resource_flags = const.GFXResourceFlagUsageSRV | const.GFXResourceFlagUsageDSV },
		}) 
	end
	
	table.iappend( registers, {
		{ name="contour_outer_ping", size_div = 1, register_flags = const.rfBackbufferBaseSize | const.rfDontClear, format = const.fmt_uint32_c1, resource_flags = const.GFXResourceFlagUsageSRV | const.GFXResourceFlagUsageUAV },
		{ name="contour_outer_pong", size_div = 1, register_flags = const.rfBackbufferBaseSize | const.rfDontClear, format = const.fmt_uint32_c1, resource_flags = const.GFXResourceFlagUsageSRV | const.GFXResourceFlagUsageUAV },
	})

	local screen = "#screen"
	local screen_ldr = "composed_ldr"

	if contour_inner then
		table.iappend( stages, {
			{ custom = "Contour Inner Objects", outputs = "contour_inner_depth" },
		})
	end

	table.iappend( stages, {
		{ custom = "Contour Outer Objects", outputs = { "#depth", "contour_outer_ping", "contour_outer_pong" } },
	})

	-- blur & desat
	-- disable old postproc until we see what to do with it
	if distance_blur then table.iappend( stages, {
		{ name = "Scene 4x down", inputs = screen, outputs = "blur0", pass = "down_4x" },
		{ name = "Scene blur", inputs = "blur0", outputs = "blur1", pass = "sqgauss" },
		{ name = "Blur + Desat", inputs = { "blur1", screen, "#depth" }, outputs = screen, pass = "blur_desat" }})
	end

	if dof then table.iappend( stages, {
		{ name = "Circle of confusion small", inputs = "#depth", outputs = "coc_small", pass = "coc_down_4x" },
		{ name = "Circle of confusion blurred", inputs = "coc_small", outputs = "coc_small_blurred", pass = "sqgauss9_coc" },
		{ name = "Circle of confusion max", inputs = { "coc_small", "coc_small_blurred" }, outputs = "coc_max", pass = "coc_max" },
		{ name = "Circle of confusion max blurred", inputs = "coc_max", outputs = "coc_small", pass = "sqgauss9_coc" },
		{ name = "DOF Medium", inputs = screen, outputs = "dof_medium", pass = "down_4x" },
		{ name = "DOF Medium Blur", inputs = "dof_medium", outputs = "dof_medium_blurred", pass = "sqgauss9" },
		{ name = "DOF High", inputs = "dof_medium_blurred", outputs = "dof_heavy", pass = "down_4x" },
		{ name = "DOF High Blur", inputs = "dof_heavy", outputs = "dof_heavy_blurred", pass = "sqgauss9" },
		{ name = "DOF apply", inputs = { screen, "dof_medium_blurred", "dof_heavy_blurred", "coc_small", "#depth" }, outputs = screen, pass = "dof_apply" }})
	end

	if radial_blur then table.iappend( stages, {
		{ name = "Radial blur", inputs = screen, outputs = screen, pass = "radial_blur", predicates = "radial_blur" }})
	end
	
	if bloom then
		stages[#stages+1] = {
			name = "Bloom Bright + 2x down AE", inputs = { screen, "#Exposure" }, outputs = "mip1", pass = "bright_pass_and_down_4x"
		}
			
		table.iappend( stages, {
			{ name = "Bloom mip2", inputs = "mip1", outputs = "mip2", pass = "down_4x" },
			{ name = "Bloom mip3", inputs = "mip2", outputs = "mip3", pass = "down_4x" },
			{ name = "Bloom mip4", inputs = "mip3", outputs = "mip4", pass = "down_4x" },
			{ name = "Bloom mip5", inputs = "mip4", outputs = "mip5", pass = "down_4x" },
			{ name = "Bloom mip6", inputs = "mip5", outputs = "mip6", pass = "down_4x" },
			
			{ name = "Bloom HGauss mip6", inputs = "mip6", outputs = "mip6hb", pass = "hgauss_tint" },
			{ name = "Bloom VGauss mip6", inputs = "mip6hb", outputs = "mip6", pass = "vgauss" },
			
			{ name = "Bloom HGauss mip5", inputs = "mip5", outputs = "mip5hb", pass = "hgauss_tint" },
			{ name = "Bloom VGauss + Add mip5", inputs = { "mip5hb", "mip6" }, outputs = "mip5" , pass = "vgauss_add" },
			
			{ name = "Bloom HGauss mip4", inputs = "mip4", outputs = "mip4hb", pass = "hgauss_tint" },
			{ name = "Bloom VGauss + Add mip4", inputs = { "mip4hb", "mip5" }, outputs = "mip4" , pass = "vgauss_add" },
			
			{ name = "Bloom HGauss mip3", inputs = "mip3", outputs = "mip3hb", pass = "hgauss_tint" },
			{ name = "Bloom VGauss + Add mip3", inputs = { "mip3hb", "mip4" }, outputs = "mip3" , pass = "vgauss_add" },
			
			{ name = "Bloom HGauss mip2", inputs = "mip2", outputs = "mip2hb", pass = "hgauss_tint" },
			{ name = "Bloom VGauss + Add mip2", inputs = { "mip2hb", "mip3" }, outputs = "mip2" , pass = "vgauss_add" },
		})
	end

	local composition_pass = false
	if bloom then
		if output_raw then
			if auto_exposure_split then
				composition_pass = "bloom_output_raw_auto_exposure_split"
			else
				composition_pass = "bloom_output_raw"
			end
		else
			if auto_exposure_split then
				composition_pass = "bloom_auto_exposure_split"
			else
				composition_pass = "bloom"
			end
		end
	else
		if output_raw then
			if auto_exposure_split then
				composition_pass = "output_raw_auto_exposure_split"
			else
				composition_pass = "output_raw"
			end
		else
			if auto_exposure_split then
				composition_pass = "auto_exposure_split"
			else
				composition_pass = "compose"
			end
		end
	end
	
	stages[#stages + 1] = {
		name = "Composition",
		inputs = { bloom and "mip2" or "#none", screen, "#Exposure", "#ColorGradingLUT" },
		outputs = { screen_ldr, output_raw and "raw" or "#none" },
		pass = composition_pass
	}
	
	if output_raw then
		table.iappend( stages, {
			{ custom = "Export Register", inputs = "raw", outputs = { { texture = hr.PostProcRAWOutputPath, format = const.fmt_float16_c3 } } },
		})
	end
	
	if contour_inner then
		local contour_inner_pass = "contour_inner"
		local contour_inner_outputs = { screen_ldr }
		if temporal then
			contour_inner_pass = contour_inner_pass .. "_motion_vectors"
			contour_inner_outputs[#contour_inner_outputs+1] = "#ReactiveMask"
			contour_inner_outputs[#contour_inner_outputs+1] = "#TransparentMask"
		end
		table.iappend( stages, {
			{ name = "Contour Inner", inputs = { "#depth", "contour_inner_depth", screen_ldr }, outputs = contour_inner_outputs, pass = contour_inner_pass, predicates = "contour_inner" },
		})
	end
	
	table.iappend( stages, {
		{ custom = "Memory Copy", inputs = screen_ldr, outputs = screen_ldr },
		{ custom = "Post Lighting Objects", inputs = { screen_ldr, "#GBufferBaseColor", "#GBufferGeometryNormal", "contour_outer_ping" }, outputs = { "#depth", screen_ldr } },
	})
	
	if object_marking then
		table.iappend( stages, {
			{ custom = "Memory Copy", inputs = screen_ldr, outputs = screen_ldr },
			{ custom = "Object Marking", inputs = { screen_ldr }, outputs = { screen_ldr, "object_marking_depth" } },
		})
	end

	do
		local grids_outputs = {screen_ldr}
		if temporal then
			grids_outputs[#grids_outputs+1] = "#ReactiveMask"
		end
	table.iappend( stages, {
		{ name = "Editor grid", inputs = { "#depth", "#GBufferBaseColor" }, outputs = grids_outputs, pass = "square_grid", predicates = "square_grid" },
		{ name = "Editor grid", inputs = { "#depth", "#GBufferBaseColor" }, outputs = grids_outputs, pass = "grid45", predicates = "grid45" },
		{ name = "Editor grid", inputs = { "#depth", "#GBufferBaseColor" }, outputs = grids_outputs, pass = "hexgrid", predicates = "hexgrid" },
		{ name = "Editor grid", inputs = { "#depth", "#GBufferBaseColor" }, outputs = grids_outputs, pass = "isolines", predicates = "isolines" },
		{ name = "Editor grid", inputs = { "#depth", "#GBufferBaseColor" }, outputs = grids_outputs, pass = "isolines2", predicates = "isolines2" },
	})
	end
	
	local handle_image_based_aa = function(screen_ldr)
		local screen_ldr_binding = { texture = screen_ldr, format = GetSRGBDataFormat(GetBackBufferFormat()) }
		if fxaa then
			table.iappend( stages, {{ name = "FXAA", inputs = { screen_ldr_binding }, outputs = { screen_ldr_binding }, pass = "fxaa" }})
		end

		if smaa then
			table.iappend( stages, {
				{ name = "SMAA_edge_detection", inputs = { screen_ldr_binding }, outputs = "SMAA_edgesTex", pass = "edge_detection" },
				{ name = "SMAA_blending_weight_calc", inputs = { "SMAA_edgesTex", "SMAA_SearchTex", "SMAA_AreaTex" }, outputs = "SMAA_blendTex", pass = "blending_weight_calc" },
				{ name = "SMAA_neighborhood_blending", inputs = { screen_ldr_binding, "SMAA_blendTex", "SMAA_edgesTex" }, outputs = { screen_ldr_binding }, pass = "neighborhood_blending" },
			})
		end
	end

	if screen_blur then
		if temporal then
			table.iappend( stages, {
				{ custom = "Temporal", inputs = { screen_ldr, "#depth" }, outputs = "fsr_upscale" },
			})
			screen_ldr = "fsr_upscale"
		end
		local screen_ldr_binding = { texture = screen_ldr, format = GetSRGBDataFormat(GetBackBufferFormat()) }
		table.iappend( stages, {
			{ name = "Screen blur HGauss", inputs = { screen_ldr_binding }, outputs = { screen_ldr_binding }, pass = "hgauss_ldr" },
			{ name = "Screen blur VGauss", inputs = { screen_ldr_binding }, outputs = "#BackBuffer", pass = "vgauss_ldr" },
		})
		screen_ldr = "#BackBuffer"
	else
		handle_image_based_aa(screen_ldr)
		if temporal then
			table.iappend( stages, {
				{ custom = "Temporal", inputs = { screen_ldr, "#depth" }, outputs = "fsr_upscale" },
			})
			screen_ldr = "fsr_upscale"
		elseif upscaling then
			if fsr_upscale then
				table.iappend( stages, {
					{ name = "FidelityFX Upscale", inputs = { { texture = screen_ldr, format = GetLinearBackbufferFormat() } }, outputs = "fsr_upscale", pass = "fsr_upscale_fp32" },
				})
			else
				table.iappend( stages, {
					{ name = "Bilinear Scaling", inputs = { { texture = screen_ldr, format = GetLinearBackbufferFormat() } }, outputs = "fsr_upscale", pass = "bilinear_scaling" },
				})
			end
			screen_ldr = "fsr_upscale"
		end
		
		local format_haze = GetSRGBDataFormat(GetBackBufferFormat())
		table.iappend( stages, {
			{ custom = "Rain Streaks", inputs = "#depth", outputs = { { texture = screen_ldr, format = format_haze } } },
			{ name = "Heat Haze", inputs = { { texture = screen_ldr, format = format_haze }, "#depth", "#TerrainHeight" }, outputs = { { texture = screen_ldr, format = format_haze } }, pass = "heat_haze", predicates = "heat_haze" },
		})
	end

	if sharpen then
		local pass_rcas = "fsr_rcas_fp32"
		table.iappend( stages, {
			{ name = "FidelityFX RCAS",    inputs = { { texture = screen_ldr, format = GetLinearBackbufferFormat() } }, outputs = "sharpen", pass = pass_rcas },
			{ custom = "Memory Copy",      inputs = "sharpen", outputs = "#BackBuffer" },
		})
	elseif screen_ldr ~= "#BackBuffer" and (screen_ldr ~= "#screen_ldr" or upscaling) then
		-- if we're rendering to #screen_ldr without upscaling, that's already the renamed #BackBuffer
		table.iappend( stages, {
			{ custom = "Memory Copy",      inputs = screen_ldr, outputs = "#BackBuffer" },
		})
	end

	if Platform.developer then
		table.iappend( stages, {
			{ name = "Debug chroma key", inputs = { "#BackBuffer", "#depth" }, outputs = "#BackBuffer", pass = "debug_chroma_key", predicates = "debug_chroma_key" },
			{ name = "Debug color pick", inputs = "#BackBuffer", outputs = "#BackBuffer", pass = "debug_color_pick", predicates = "debug_color_pick" },
		})
	end
	
	if rawget(_G, "g_PhotoFilter") and g_PhotoFilter.pass ~= "" then
		local input_textures = { "#BackBuffer", "#depth", "#GBufferNormal", "#GBufferBaseColor", "#GBufferRM" }

		if g_PhotoFilter.tex1 and g_PhotoFilter.tex1 ~= "" then
			registers[#registers+1] = { name = "filter_tex_1", texture = g_PhotoFilter.tex1, id="filter_tex_1" }
			input_textures[#input_textures + 1] = "filter_tex_1"
		end
		if g_PhotoFilter.tex2 and g_PhotoFilter.tex2 ~= "" then
			registers[#registers+1] = { name = "filter_tex_2", texture = g_PhotoFilter.tex2, id="filter_tex_2" }
			input_textures[#input_textures + 1] =  "filter_tex_2"
		end

		
		table.iappend( stages, {
			{ name = "Photo Filter", inputs = input_textures, outputs = "#BackBuffer", pass = string.lower(g_PhotoFilter.pass) },
		})
	end
	
	if Platform.developer then
		local postproc_debug_mode = rawget(_G, "g_PostProcDebugMode")
		if postproc_debug_mode ~= "Off" then
			table.iappend( stages, {
				{ name = "Post Proc Debug", inputs = { "#BackBuffer" }, outputs = "#BackBuffer", pass = postproc_debug_mode },
			})
		end
	end
	
	PostProc_SetRegistersAndStages(registers, stages)
end

local RebuildThread = false

---
--- Rebuilds the post-processing pipeline.
---
--- This function is responsible for rebuilding the post-processing pipeline, which is used to apply various visual effects to the game's rendered output. It does this by creating a new real-time thread that calls the `PP_RebuildInternal()` function, which is responsible for the actual pipeline rebuild logic.
---
--- Once the pipeline has been rebuilt, the `RebuildThread` variable is set to `false` to indicate that the rebuild process has completed.
---
--- @function PP_Rebuild
--- @return nil
function PP_Rebuild()
	DeleteThread(RebuildThread)
	RebuildThread = CreateRealTimeThread(function()
		PP_RebuildInternal()
		RebuildThread = false
	end)
end

---
--- Registers the SMAA search and area textures with the ResourceManager.
---
--- This function is called in response to the `ReportResources` message, which is used to report the resources that are required by the game. It retrieves the resource IDs for the SMAA search and area textures, and adds them to the `resIds` table, which is used to track the required resources.
---
--- @param resIds table The table of resource IDs to be reported.
--- @return nil
function OnMsg.ReportResources(resIds)
	local SMAA_SearchTex_id = ResourceManager.GetResourceID("CommonAssets/System/SMAA_SearchTex.dds")
	local SMAA_AreaTex_id = ResourceManager.GetResourceID("CommonAssets/System/SMAA_AreaTex.dds")
	resIds[SMAA_SearchTex_id] = ""
	resIds[SMAA_AreaTex_id] = ""
end