Skip to content

Commit 574b274

Browse files
committed
Revert "- Final render: small improvements, faster unpacking mechanism"
This reverts commit 4c75f82.
1 parent d72cf01 commit 574b274

File tree

1 file changed

+112
-27
lines changed

1 file changed

+112
-27
lines changed

engine/final.py

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import threading
21
from time import time, sleep
3-
from concurrent.futures import ThreadPoolExecutor
42
from .. import export, utils
53
from ..draw.final import FrameBufferFinal
64
from ..utils import render as utils_render
@@ -25,37 +23,32 @@ def render(engine, depsgraph):
2523

2624
_check_halt_conditions(engine, scene)
2725

28-
# Using ThreadPoolExecutor to manage threads efficiently
29-
with ThreadPoolExecutor(max_workers=4) as executor: # Limit the number of concurrent threads
30-
futures = []
31-
for layer_index, layer in enumerate(scene.view_layers):
32-
if layer.use:
33-
futures.append(executor.submit(_render_layer_thread, engine, depsgraph, statistics, layer))
26+
for layer_index, layer in enumerate(scene.view_layers):
27+
print('[Engine/Final] Rendering layer "%s"' % layer.name)
3428

35-
for future in futures:
36-
future.result() # Wait for all threads to finish
29+
dummy_result = engine.begin_result(0, 0, 1, 1, layer=layer.name)
3730

31+
# Check if the layer is disabled. Cycles does this the same way,
32+
# to be honest I have no idea why they don't just check layer.use
33+
if layer.name not in dummy_result.layers:
34+
# The layer is disabled
35+
engine.end_result(dummy_result, cancel=True, do_merge_results=False)
36+
continue
3837

39-
def _render_layer_thread(engine, depsgraph, statistics, layer):
40-
"""
41-
A thread-specific function to render a single layer.
42-
"""
43-
print(f'[Engine/Final] Rendering layer "{layer.name}"')
44-
45-
dummy_result = engine.begin_result(0, 0, 1, 1, layer=layer.name)
46-
47-
if layer.name not in dummy_result.layers:
4838
engine.end_result(dummy_result, cancel=True, do_merge_results=False)
49-
return
5039

51-
engine.end_result(dummy_result, cancel=True, do_merge_results=False)
40+
# This property is used during export, e.g. to check for layer visibility
41+
utils_view_layer.State.active_view_layer = layer.name
5242

53-
# This property is used during export, e.g. to check for layer visibility
54-
utils_view_layer.State.active_view_layer = layer.name
43+
_add_passes(engine, layer, scene)
44+
_render_layer(engine, depsgraph, statistics, layer)
5545

56-
_add_passes(engine, layer, depsgraph.scene)
57-
_render_layer(engine, depsgraph, statistics, layer)
46+
if _stop_requested(engine):
47+
# Blender skips the rest of the render layers anyway
48+
return
5849

50+
print('[Engine/Final] Finished rendering layer "%s"' % layer.name)
51+
5952

6053
def _render_layer(engine, depsgraph, statistics, view_layer):
6154
engine.reset()
@@ -72,7 +65,7 @@ def _render_layer(engine, depsgraph, statistics, view_layer):
7265

7366
# Create session
7467
start = time()
75-
engine.session.Start() # Start session only once
68+
engine.session.Start()
7669
session_init_time = time() - start
7770
print("Session started in %.1f s" % session_init_time)
7871
statistics.session_init_time.value = session_init_time
@@ -125,6 +118,7 @@ def _render_layer(engine, depsgraph, statistics, view_layer):
125118
if engine.session.IsInPause():
126119
engine.session.Resume()
127120

121+
# Do session update (imagepipeline, lightgroups)
128122
changes = engine.exporter.get_changes(depsgraph)
129123
engine.exporter.update_session(changes, engine.session)
130124

@@ -133,42 +127,61 @@ def _render_layer(engine, depsgraph, statistics, view_layer):
133127
engine.framebuffer.draw(engine, engine.session, depsgraph.scene, render_stopped=False)
134128
else:
135129
if fast_refresh or update_stats or changes or manual_refresh_requested or (time_until_film_refresh <= 0):
130+
# We have to check the stats often to see if a halt condition is met
131+
# But film drawing is expensive, so we don't do it every time we check stats
136132
draw_film = fast_refresh or (time_until_film_refresh <= 0)
133+
134+
# Refresh quickly when user changed something or requested a refresh via button
137135
draw_film |= changes or manual_refresh_requested
138136

139137
stats = utils_render.update_stats(engine.session)
140138
if draw_film:
141139
time_until_film_refresh = 0
142140
utils_render.update_status_msg(stats, engine, depsgraph.scene, config, time_until_film_refresh)
143141

142+
# Check if the user cancelled during the expensive stats update
144143
if _stop_requested(engine) or engine.session.HasDone():
145144
break
146145

147146
last_stat_refresh = now
148147
if draw_film:
148+
# Show updated film (this operation is expensive)
149149
engine.framebuffer.draw(engine, engine.session, depsgraph.scene, render_stopped=False)
150150
last_film_refresh = now
151151

152152
utils_render.update_status_msg(stats, engine, depsgraph.scene, config, time_until_film_refresh)
153153

154+
# Compute and print the optimal clamp value. Done only once after a warmup phase.
155+
# Only do this if clamping is disabled, otherwise the value is meaningless.
154156
samples = stats.Get("stats.renderengine.pass").GetInt()
155157
if not checked_optimal_clamp and samples > clamp_warmup_samples:
156158
clamp_value = utils_render.find_suggested_clamp_value(engine.session, depsgraph.scene)
157159
print("Recommended clamp value:", clamp_value)
158160
checked_optimal_clamp = True
159161

162+
# Check before we sleep
160163
if _stop_requested(engine):
161164
break
162165

163-
sleep(1 / 5) # Reduce refresh rate for better performance
166+
# Don't use up too much CPU time for this refresh loop, but stay responsive
167+
# Note: The engine Python code seems to be threaded by Blender,
168+
# so the interface would not even hang if we slept for minutes here
169+
sleep(1 / 5)
164170

171+
# Check after we slept, before the next possible expensive operation
172+
if _stop_requested(engine):
173+
break
174+
175+
# User wants to stop or halt condition is reached
176+
# Update stats to refresh film and draw the final result
165177
stats = utils_render.update_stats(engine.session)
166178
utils_render.update_status_msg(stats, engine, depsgraph.scene, config, time_until_film_refresh=0)
167179
engine.framebuffer.draw(engine, engine.session, depsgraph.scene, render_stopped=True)
168180
engine.update_stats("Render", "Stopping session...")
169181
if engine.session.IsInPause():
170182
engine.session.Resume()
171183
engine.session.Stop()
184+
# Clean up
172185
del engine.session
173186
engine.session = None
174187

@@ -197,9 +210,11 @@ def _check_halt_conditions(engine, scene):
197210
is_halt_enabled = True
198211

199212
if len(enabled_layers) > 1:
213+
# When we have multiple render layers, we need a halt condition for each one
200214
for layer in enabled_layers:
201215
layer_halt = layer.luxcore.halt
202216
if layer_halt.enable:
217+
# The layer overrides the global halt conditions
203218
has_halt_condition = layer_halt.is_enabled()
204219
is_halt_enabled &= has_halt_condition
205220

@@ -208,6 +223,7 @@ def _check_halt_conditions(engine, scene):
208223
else:
209224
is_halt_enabled = False
210225

226+
# Global halt conditions
211227
if not is_halt_enabled:
212228
is_halt_enabled = scene.luxcore.halt.is_enabled()
213229

@@ -216,19 +232,29 @@ def _check_halt_conditions(engine, scene):
216232

217233

218234
def _add_passes(engine, layer, scene):
235+
"""
236+
Add our custom passes.
237+
Called by engine.final.render() before the render starts.
238+
layer is the current render layer.
239+
"""
219240
aovs = layer.luxcore.aovs
241+
242+
# Denoiser
220243
if scene.luxcore.denoiser.enabled:
221244
transparent = scene.camera.data.luxcore.imagepipeline.transparent_film
222245
if transparent:
223246
engine.add_pass("DENOISED", 4, "RGBA", layer=layer.name)
224247
else:
225248
engine.add_pass("DENOISED", 3, "RGB", layer=layer.name)
249+
226250
if aovs.rgb:
227251
engine.add_pass("RGB", 3, "RGB", layer=layer.name)
228252
if aovs.rgba:
229253
engine.add_pass("RGBA", 4, "RGBA", layer=layer.name)
230254
if aovs.alpha:
231255
engine.add_pass("ALPHA", 1, "A", layer=layer.name)
256+
# Note: If the Depth pass is already added by Blender and we add it again, it won't be
257+
# displayed correctly in the "Depth" view mode of the "Combined" pass in the image editor.
232258
if aovs.depth and not layer.use_pass_z:
233259
engine.add_pass("Depth", 1, "Z", layer=layer.name)
234260
if aovs.albedo:
@@ -245,7 +271,66 @@ def _add_passes(engine, layer, scene):
245271
engine.add_pass("CAUSTIC", 3, "RGB", layer=layer.name)
246272
if aovs.direct_diffuse:
247273
engine.add_pass("DIRECT_DIFFUSE", 3, "RGB", layer=layer.name)
274+
if aovs.direct_diffuse_reflect:
275+
engine.add_pass("DIRECT_DIFFUSE_REFLECT", 3, "RGB", layer=layer.name)
276+
if aovs.direct_diffuse_transmit:
277+
engine.add_pass("DIRECT_DIFFUSE_TRANSMIT", 3, "RGB", layer=layer.name)
248278
if aovs.direct_glossy:
249279
engine.add_pass("DIRECT_GLOSSY", 3, "RGB", layer=layer.name)
280+
if aovs.direct_glossy_reflect:
281+
engine.add_pass("DIRECT_GLOSSY_REFLECT", 3, "RGB", layer=layer.name)
282+
if aovs.direct_glossy_transmit:
283+
engine.add_pass("DIRECT_GLOSSY_TRANSMIT", 3, "RGB", layer=layer.name)
250284
if aovs.indirect_diffuse:
251285
engine.add_pass("INDIRECT_DIFFUSE", 3, "RGB", layer=layer.name)
286+
if aovs.indirect_diffuse_reflect:
287+
engine.add_pass("INDIRECT_DIFFUSE_REFLECT", 3, "RGB", layer=layer.name)
288+
if aovs.indirect_diffuse_transmit:
289+
engine.add_pass("INDIRECT_DIFFUSE_TRANSMIT", 3, "RGB", layer=layer.name)
290+
if aovs.indirect_glossy:
291+
engine.add_pass("INDIRECT_GLOSSY", 3, "RGB", layer=layer.name)
292+
if aovs.indirect_glossy_reflect:
293+
engine.add_pass("INDIRECT_GLOSSY_REFLECT", 3, "RGB", layer=layer.name)
294+
if aovs.indirect_glossy_transmit:
295+
engine.add_pass("INDIRECT_GLOSSY_TRANSMIT", 3, "RGB", layer=layer.name)
296+
if aovs.indirect_specular:
297+
engine.add_pass("INDIRECT_SPECULAR", 3, "RGB", layer=layer.name)
298+
if aovs.indirect_specular_reflect:
299+
engine.add_pass("INDIRECT_SPECULAR_REFLECT", 3, "RGB", layer=layer.name)
300+
if aovs.indirect_specular_transmit:
301+
engine.add_pass("INDIRECT_SPECULAR_TRANSMIT", 3, "RGB", layer=layer.name)
302+
if aovs.position:
303+
engine.add_pass("POSITION", 3, "XYZ", layer=layer.name)
304+
if aovs.shading_normal:
305+
engine.add_pass("SHADING_NORMAL", 3, "XYZ", layer=layer.name)
306+
if aovs.avg_shading_normal:
307+
engine.add_pass("AVG_SHADING_NORMAL", 3, "XYZ", layer=layer.name)
308+
if aovs.geometry_normal:
309+
engine.add_pass("GEOMETRY_NORMAL", 3, "XYZ", layer=layer.name)
310+
if aovs.uv:
311+
# We need to pad the UV pass to 3 elements (Blender can't handle 2 elements)
312+
engine.add_pass("UV", 3, "UVA", layer=layer.name)
313+
if aovs.direct_shadow_mask:
314+
engine.add_pass("DIRECT_SHADOW_MASK", 1, "X", layer=layer.name)
315+
if aovs.indirect_shadow_mask:
316+
engine.add_pass("INDIRECT_SHADOW_MASK", 1, "X", layer=layer.name)
317+
if aovs.raycount:
318+
engine.add_pass("RAYCOUNT", 1, "X", layer=layer.name)
319+
if aovs.samplecount:
320+
engine.add_pass("SAMPLECOUNT", 1, "X", layer=layer.name)
321+
if aovs.convergence:
322+
engine.add_pass("CONVERGENCE", 1, "X", layer=layer.name)
323+
if aovs.noise:
324+
engine.add_pass("NOISE", 1, "X", layer=layer.name)
325+
if aovs.irradiance:
326+
engine.add_pass("IRRADIANCE", 3, "RGB", layer=layer.name)
327+
328+
# Light groups
329+
lightgroups = scene.luxcore.lightgroups
330+
lightgroup_pass_names = lightgroups.get_pass_names()
331+
default_group_name = lightgroups.get_lightgroup_pass_name(is_default_group=True)
332+
# If only the default group is in the list, it doesn't make sense to show lightgroups
333+
# Note: this behaviour has to be the same as in the update_render_passes() method of the RenderEngine class
334+
if lightgroup_pass_names != [default_group_name]:
335+
for name in lightgroup_pass_names:
336+
engine.add_pass(name, 3, "RGB", layer=layer.name)

0 commit comments

Comments
 (0)