#!/usr/bin/env python3 # SPDX-FileCopyrightText: 2015-2022 Blender Authors # # SPDX-License-Identifier: Apache-2.0 import argparse import os import pathlib import subprocess import sys from pathlib import Path try: # Render report is not always available and leads to errors in the console logs that can be ignored. from modules import render_report class EEVEEReport(render_report.Report): def __init__(self, title, output_dir, oiiotool, variation=None, blocklist=[]): super().__init__(title, output_dir, oiiotool, variation=variation, blocklist=blocklist) self.gpu_backend = variation def _get_render_arguments(self, arguments_cb, filepath, base_output_filepath): return arguments_cb(filepath, base_output_filepath, gpu_backend=self.gpu_backend) except ImportError: # render_report can only be loaded when running the render tests. It errors when # this script is run during preparation steps. pass # List of .blend files that are known to be failing and are not ready to be # tested, or that only make sense on some devices. Accepts regular expressions. BLOCKLIST = [ # Blocked due to point cloud volume differences between platforms (to be fixed). "points_volume.blend", # Blocked due to GBuffer encoding of small IOR difference between platforms (to be fixed). "principled_bsdf_thinfilm_transmission.blend", "ray_offset.blend", # Blocked due to difference in border texel handling between platforms (to be fixed). "render_passes_thinfilm_color.blend", # Blocked due to a significant amount of transparency that have a different nosie pattern between devices. "light_path_is_shadow_ray.blend", # Blocked as the test seems to alternate between two different states "light_path_is_diffuse_ray.blend", ] BLOCKLIST_METAL = [ # Blocked due to difference in volume lightprobe bakes (to be fixed). "clamp_.*.blend", "shadow_all_max_bounces.blend", "light_link_exclude.blend", "light_link_instanced_receiver.blend", "light_path_is_volume_scatter_ray.blend", # Blocked due to difference in volume lightprobe bakes(maybe?) (to be fixed). "volume_zero_extinction_channel.blend", # Blocked due to difference in screen space tracing (to be fixed). "sss_reflection_clamp.blend", # Blocked due to difference in volume rendering (to be fixed). "principled_bsdf_interior.blend", # Blocked due to difference in mipmap interpolation (to be fixed). "environment_mirror_ball.blend", # Blocked due to difference in mipmap interpolation / anisotropic filtering (to be fixed). "image.blend" ] BLOCKLIST_VULKAN = [ # Blocked due to difference in screen space tracing (to be fixed). "sss_reflection_clamp.blend", # Blocked due to difference in screen space tracing (to be investigated). "image.blend" ] def setup(): import bpy for scene in bpy.data.scenes: scene.render.engine = 'BLENDER_EEVEE_NEXT' # Enable Eevee features eevee = scene.eevee # Overscan of 50 will doesn't offset the final image, and adds more information for screen based ray tracing. eevee.use_overscan = True eevee.overscan_size = 50.0 # Ambient Occlusion Pass eevee.gtao_distance = 1 # Lights eevee.light_threshold = 0.001 # Hair scene.render.hair_type = 'STRIP' # Shadow eevee.shadow_step_count = 16 # Volumetric eevee.volumetric_tile_size = '2' eevee.volumetric_start = 1.0 eevee.volumetric_end = 50.0 eevee.volumetric_samples = 128 eevee.use_volumetric_shadows = True eevee.clamp_volume_indirect = 0.0 # Motion Blur if scene.render.use_motion_blur: eevee.motion_blur_steps = 10 # Ray-tracing eevee.use_raytracing = True eevee.ray_tracing_method = 'SCREEN' ray_tracing = eevee.ray_tracing_options ray_tracing.resolution_scale = "1" ray_tracing.screen_trace_quality = 1.0 ray_tracing.screen_trace_thickness = 1.0 # Light-probes eevee.gi_cubemap_resolution = '256' # Only include the plane in probes for ob in scene.objects: if ob.type == 'LIGHT': # Set maximum resolution ob.data.shadow_maximum_resolution = 0.0 if ob.name != 'Plane' and ob.type != 'LIGHT': ob.hide_probe_volume = True ob.hide_probe_sphere = True ob.hide_probe_plane = True # Counteract the versioning from legacy EEVEE. Should be changed per file at some point. for mat_slot in ob.material_slots: if mat_slot.material: mat_slot.material.thickness_mode = 'SPHERE' if bpy.data.objects.get('Volume_Probe_Baked') is not None: # Some file already have pre existing probe setup with baked data. pass # Does not work in edit mode elif bpy.context.mode == 'OBJECT': # Simple probe setup bpy.ops.object.lightprobe_add(type='SPHERE', location=(0.0, 0.1, 1.0)) cubemap = bpy.context.selected_objects[0] cubemap.scale = (5.0, 5.0, 2.0) cubemap.data.falloff = 0.0 cubemap.data.clip_start = 0.8 cubemap.data.influence_distance = 1.2 bpy.ops.object.lightprobe_add(type='VOLUME', location=(0.0, 0.0, 2.0)) grid = bpy.context.selected_objects[0] grid.scale = (8.0, 4.5, 4.5) grid.data.resolution_x = 32 grid.data.resolution_y = 16 grid.data.resolution_z = 8 grid.data.bake_samples = 128 grid.data.capture_world = True grid.data.surfel_density = 100 # Make lighting smoother for most of the case. grid.data.dilation_threshold = 1.0 bpy.ops.object.lightprobe_cache_bake(subset='ACTIVE') # When run from inside Blender, render and exit. try: import bpy inside_blender = True except ImportError: inside_blender = False if inside_blender: try: setup() except Exception as e: print(e) sys.exit(1) def get_gpu_device_type(blender): # TODO: This always fails. command = [ blender, "--background", "--factory-startup", "--python", str(pathlib.Path(__file__).parent / "gpu_info.py") ] try: completed_process = subprocess.run(command, stdout=subprocess.PIPE) for line in completed_process.stdout.read_text(): if line.startswith("GPU_DEVICE_TYPE:"): vendor = line.split(':')[1] return vendor except Exception: return None return None def get_arguments(filepath, output_filepath, gpu_backend): arguments = [ "--background", "--factory-startup", "--enable-autoexec", "--debug-memory", "--debug-exit-on-error"] if gpu_backend: arguments.extend(["--gpu-backend", gpu_backend]) arguments.extend([ filepath, "-E", "BLENDER_EEVEE_NEXT", "-P", os.path.realpath(__file__), "-o", output_filepath, "-F", "PNG", "-f", "1"]) return arguments def create_argparse(): parser = argparse.ArgumentParser( description="Run test script for each blend file in TESTDIR, comparing the render result with known output." ) parser.add_argument("--blender", required=True) parser.add_argument("--testdir", required=True) parser.add_argument("--outdir", required=True) parser.add_argument("--oiiotool", required=True) parser.add_argument('--batch', default=False, action='store_true') parser.add_argument('--gpu-backend') return parser def main(): parser = create_argparse() args = parser.parse_args() gpu_device_type = get_gpu_device_type(args.blender) reference_override_dir = None if gpu_device_type == "AMD": reference_override_dir = "eevee_next_renders/amd" blocklist = BLOCKLIST if args.gpu_backend == "metal": blocklist += BLOCKLIST_METAL elif args.gpu_backend == "vulkan": blocklist += BLOCKLIST_VULKAN report = EEVEEReport("Eevee Next", args.outdir, args.oiiotool, variation=args.gpu_backend, blocklist=blocklist) if args.gpu_backend == "vulkan": report.set_compare_engine('eevee_next', 'opengl') else: report.set_compare_engine('cycles', 'CPU') report.set_pixelated(True) report.set_reference_dir("eevee_next_renders") report.set_reference_override_dir(reference_override_dir) test_dir_name = Path(args.testdir).name if test_dir_name.startswith('image_mapping'): # Platform dependent border values. To be fixed report.set_fail_threshold(0.2) elif test_dir_name.startswith('image'): report.set_fail_threshold(0.051) elif test_dir_name.startswith('displacement'): # metal shadow and wireframe difference. To be fixed. report.set_fail_threshold(0.07) # Noise pattern changes depending on platform. Mostly caused by transparency. # TODO(fclem): See if we can just increase number of samples per file. if test_dir_name.startswith('render_layer'): # shadow pass, rlayer flag report.set_fail_threshold(0.035) elif test_dir_name.startswith('hair'): # hair close up report.set_fail_threshold(0.0275) elif test_dir_name.startswith('integrator'): # Noise difference in transparent materials report.set_fail_threshold(0.05) elif test_dir_name.startswith('pointcloud'): # points transparent report.set_fail_threshold(0.06) elif test_dir_name.startswith('light_linking'): # Noise difference in transparent material report.set_fail_threshold(0.05) ok = report.run(args.testdir, args.blender, get_arguments, batch=args.batch) sys.exit(not ok) if not inside_blender and __name__ == "__main__": main()