Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <string>
namespace blender {
struct Image;
/* Create python module _cycles used by addon. */
void *CCL_python_module_init();
void CCL_log_init();
void CCL_implicit_sharing_init();
/* Texture cache generation. */
bool CCL_resolve_texture_cache(const Image *image,
const char *filepath,
const char *texture_cache_directory,
std::string &r_tx_filepath);
bool CCL_generate_texture_cache(const Image *image,
const char *filepath,
const char *texture_cache_directory = "");
} // namespace blender

View File

@@ -0,0 +1,113 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
..
../../../source/blender/makesrna
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
camera.cpp
device.cpp
display_driver.cpp
image.cpp
implicit_sharing.cpp
geometry.cpp
light.cpp
light_linking.cpp
mesh.cpp
object.cpp
object_cull.cpp
output_driver.cpp
particles.cpp
pointcloud.cpp
curves.cpp
logging.cpp
python.cpp
session.cpp
shader.cpp
sync.cpp
texture_cache.cpp
viewport.cpp
volume.cpp
attribute_convert.h
CCL_api.h
device.h
display_driver.h
id_map.h
image.h
light_linking.h
object_cull.h
output_driver.h
sync.h
session.h
util.h
viewport.h
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::depsgraph
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::imbuf
PRIVATE bf::geometry
PRIVATE bf::gpu
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::clog
PRIVATE bf::nodes
PRIVATE bf::nodes::shader
PRIVATE bf::render
PRIVATE cycles_bvh
PRIVATE cycles_device
PRIVATE cycles_graph
PRIVATE cycles_kernel
PRIVATE cycles_scene
PRIVATE cycles_session
PRIVATE cycles_subd
PRIVATE cycles_util
PRIVATE bf::dependencies::epoxy
PRIVATE bf::dependencies::optional::python
PRIVATE bf::dependencies::optional::openimagedenoise
PRIVATE bf::dependencies::optional::openvdb
PRIVATE bf::dependencies::optional::osl
)
set(ADDON_FILES
addon/__init__.py
addon/camera.py
addon/engine.py
addon/maketx.py
addon/operators.py
addon/osl.py
addon/presets.py
addon/properties.py
addon/ui.py
addon/version_update.py
)
if(WITH_CYCLES_DEVICE_HIP)
add_definitions(-DWITH_HIP)
endif()
if(WITH_CYCLES_DEVICE_METAL)
add_definitions(-DWITH_METAL)
endif()
if(WITH_MOD_FLUID)
add_definitions(-DWITH_FLUID)
endif()
blender_add_lib(bf_intern_cycles "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_dependencies(bf_intern_cycles bf_rna)
delayed_install(${CMAKE_CURRENT_SOURCE_DIR} "${ADDON_FILES}" ${CYCLES_INSTALL_PATH})

View File

@@ -0,0 +1,185 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
bl_info = {
"name": "Cycles Render Engine",
"author": "",
"blender": (2, 80, 0),
"description": "Cycles renderer integration",
"warning": "",
"doc_url": "https://docs.blender.org/manual/en/latest/render/cycles/",
"tracker_url": "",
"support": 'OFFICIAL',
"category": "Render"}
# Support 'reload' case.
if "bpy" in locals():
import importlib
if "engine" in locals():
importlib.reload(engine)
if "version_update" in locals():
importlib.reload(version_update)
if "ui" in locals():
importlib.reload(ui)
if "operators" in locals():
importlib.reload(operators)
if "properties" in locals():
importlib.reload(properties)
if "presets" in locals():
importlib.reload(presets)
if "maketx" in locals():
importlib.reload(maketx)
import bpy
from . import (
engine,
version_update,
)
class CyclesRender(bpy.types.RenderEngine):
bl_idname = 'CYCLES'
bl_label = "Cycles"
bl_use_eevee_viewport = True
bl_use_preview = True
bl_use_exclude_layers = True
bl_use_spherical_stereo = True
bl_use_custom_freestyle = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.session = None
def __del__(self):
engine.free(self)
# final render
def update(self, data, depsgraph):
if not self.session:
if self.is_preview:
cscene = bpy.context.scene.cycles
use_osl = cscene.shading_system
engine.create(self, data, preview_osl=use_osl)
else:
engine.create(self, data)
engine.reset(self, data, depsgraph)
def render(self, depsgraph):
engine.render(self, depsgraph)
def render_frame_finish(self):
engine.render_frame_finish(self)
def draw(self, context, depsgraph):
engine.draw(self, depsgraph, context.space_data)
def bake(self, depsgraph, obj, pass_type, pass_filter, width, height):
engine.bake(self, depsgraph, obj, pass_type, pass_filter, width, height)
# viewport render
def view_update(self, context, depsgraph):
if not self.session:
# When starting a new render session in viewport (by switching
# viewport to Rendered shading) unpause the render. The way to think
# of it is: artist requests render, so we start to render.
# Do it for both original and evaluated scene so that Cycles
# immediately reacts to un-paused render.
cscene = context.scene.cycles
cscene_eval = depsgraph.scene_eval.cycles
if cscene.preview_pause or cscene_eval.preview_pause:
cscene.preview_pause = False
cscene_eval.preview_pause = False
engine.create(self, context.blend_data,
context.region, context.space_data, context.region_data)
engine.reset(self, context.blend_data, depsgraph)
engine.sync(self, depsgraph, context.blend_data)
def view_draw(self, context, depsgraph):
engine.view_draw(self, depsgraph, context.region, context.space_data, context.region_data)
def update_script_node(self, node):
if engine.with_osl():
from . import osl
osl.update_script_node(node, self.report)
else:
self.report({'ERROR'}, "OSL support disabled in this build")
def update_custom_camera(self, cam):
if engine.with_osl():
from . import osl
osl.update_custom_camera_shader(cam, self.report)
else:
self.report({'ERROR'}, "OSL support disabled in this build")
def update_render_passes(self, scene, srl):
engine.register_passes(self, scene, srl)
def engine_exit():
engine.exit()
classes = (
CyclesRender,
)
cli_commands = []
def register():
from bpy.utils import register_class
from . import ui
from . import operators
from . import properties
from . import presets
from .maketx import maketx_command
import atexit
# Make sure we only registered the callback once.
atexit.unregister(engine_exit)
atexit.register(engine_exit)
engine.init()
properties.register()
ui.register()
operators.register()
presets.register()
for cls in classes:
register_class(cls)
bpy.app.handlers.version_update.append(version_update.do_versions)
cli_commands.append(bpy.utils.register_cli_command("maketx", maketx_command))
def unregister():
from bpy.utils import unregister_class
from . import ui
from . import operators
from . import properties
from . import presets
bpy.app.handlers.version_update.remove(version_update.do_versions)
for cmd in cli_commands:
bpy.utils.unregister_cli_command(cmd)
cli_commands.clear()
ui.unregister()
operators.unregister()
properties.unregister()
presets.unregister()
for cls in classes:
unregister_class(cls)

View File

@@ -0,0 +1,78 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
# Fit to match default projective camera with focal_length 50 and sensor_width 36.
default_fisheye_polynomial = [
-1.1735143712967577e-05,
-0.019988736953434998,
-3.3525322965709175e-06,
3.099275275886036e-06,
-2.6064646454854524e-08,
]
# Utilities to generate lens polynomials to match built-in camera types, only here
# for reference at the moment, not used by the code.
def create_grid(sensor_height, sensor_width):
import numpy as np
if sensor_height is None:
sensor_height = sensor_width / (16 / 9) # Default aspect ration 16:9
uu, vv = np.meshgrid(np.linspace(0, 1, 100), np.linspace(0, 1, 100))
uu = (uu - 0.5) * sensor_width
vv = (vv - 0.5) * sensor_height
rr = np.sqrt(uu ** 2 + vv ** 2)
return rr
def fisheye_lens_polynomial_from_projective(focal_length=50, sensor_width=36, sensor_height=None):
import numpy as np
rr = create_grid(sensor_height, sensor_width)
polynomial = np.polyfit(rr.flat, (-np.arctan(rr / focal_length)).flat, 4)
return list(reversed(polynomial))
def fisheye_lens_polynomial_from_projective_fov(fov, sensor_width=36, sensor_height=None):
import numpy as np
f = sensor_width / 2 / np.tan(fov / 2)
return fisheye_lens_polynomial_from_projective(f, sensor_width, sensor_height)
def fisheye_lens_polynomial_from_equisolid(lens=10.5, sensor_width=36, sensor_height=None):
import numpy as np
rr = create_grid(sensor_height, sensor_width)
x = rr.reshape(-1)
x = np.stack([x**i for i in [1, 2, 3, 4]])
y = (-2 * np.arcsin(rr / (2 * lens))).reshape(-1)
polynomial = np.linalg.lstsq(x.T, y.T, rcond=None)[0]
return [0] + list(polynomial)
def fisheye_lens_polynomial_from_equidistant(fov=180, sensor_width=36, sensor_height=None):
import numpy as np
return [0, -np.radians(fov) / sensor_width, 0, 0, 0]
def fisheye_lens_polynomial_from_distorted_projective_polynomial(
k1, k2, k3, focal_length=50, sensor_width=36, sensor_height=None,
):
import numpy as np
rr = create_grid(sensor_height, sensor_width)
r2 = (rr / focal_length) ** 2
r4 = r2 * r2
r6 = r4 * r2
r_coeff = 1 + k1 * r2 + k2 * r4 + k3 * r6
polynomial = np.polyfit(rr.flat, (-np.arctan(rr / focal_length * r_coeff)).flat, 4)
return list(reversed(polynomial))
def fisheye_lens_polynomial_from_distorted_projective_divisions(
k1, k2, focal_length=50, sensor_width=36, sensor_height=None,
):
import numpy as np
rr = create_grid(sensor_height, sensor_width)
r2 = (rr / focal_length) ** 2
r4 = r2 * r2
r_coeff = 1 + k1 * r2 + k2 * r4
polynomial = np.polyfit(rr.flat, (-np.arctan(rr / focal_length / r_coeff)).flat, 4)
return list(reversed(polynomial))

View File

@@ -0,0 +1,277 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
def _configure_argument_parser():
import argparse
# No help because it conflicts with general Python scripts argument parsing
parser = argparse.ArgumentParser(description="Cycles Addon argument parser",
add_help=False)
parser.add_argument("--cycles-print-stats",
help="Print rendering statistics to stderr",
action='store_true')
parser.add_argument("--cycles-device",
help="Set the device to use for Cycles, overriding user preferences and the scene setting."
"Valid options are 'CPU', 'CUDA', 'OPTIX', 'HIP', 'ONEAPI', or 'METAL'."
"Additionally, you can append '+CPU' to any GPU type for hybrid rendering.",
default=None)
return parser
def _parse_command_line():
import sys
argv = sys.argv
if "--" not in argv:
return
parser = _configure_argument_parser()
args, _ = parser.parse_known_args(argv[argv.index("--") + 1:])
if args.cycles_print_stats:
import _cycles
_cycles.enable_print_stats()
if args.cycles_device:
import _cycles
if not _cycles.set_device_override(args.cycles_device):
sys.exit(1)
def init():
import bpy
import _cycles
import os.path
path = os.path.dirname(__file__)
user_path = os.path.dirname(os.path.abspath(bpy.utils.user_resource('CONFIG', path='')))
_cycles.init(path, user_path, bpy.app.background)
_parse_command_line()
def exit():
import _cycles
_cycles.exit()
def create(engine, data, region=None, v3d=None, rv3d=None, preview_osl=False):
import _cycles
import bpy
data = data.as_pointer()
prefs = bpy.context.preferences.as_pointer()
screen = 0
if region:
screen = region.id_data.as_pointer()
region = region.as_pointer()
if v3d:
screen = screen or v3d.id_data.as_pointer()
v3d = v3d.as_pointer()
if rv3d:
screen = screen or rv3d.id_data.as_pointer()
rv3d = rv3d.as_pointer()
engine.session = _cycles.create(engine.as_pointer(), prefs, data, screen, region, v3d, rv3d, preview_osl)
def free(engine):
if hasattr(engine, "session"):
if engine.session:
import _cycles
_cycles.free(engine.session)
del engine.session
def render(engine, depsgraph):
import _cycles
if hasattr(engine, "session"):
_cycles.render(engine.session, depsgraph.as_pointer())
def render_frame_finish(engine):
if not engine.session:
return
import _cycles
_cycles.render_frame_finish(engine.session)
def draw(engine, depsgraph, space_image):
if not engine.session:
return
depsgraph_ptr = depsgraph.as_pointer()
space_image_ptr = space_image.as_pointer()
screen_ptr = space_image.id_data.as_pointer()
import _cycles
_cycles.draw(engine.session, depsgraph_ptr, screen_ptr, space_image_ptr)
def bake(engine, depsgraph, obj, pass_type, pass_filter, width, height):
import _cycles
session = getattr(engine, "session", None)
if session is not None:
_cycles.bake(engine.session, depsgraph.as_pointer(), obj.as_pointer(), pass_type, pass_filter, width, height)
def reset(engine, data, depsgraph):
import _cycles
import bpy
prefs = bpy.context.preferences
if prefs.experimental.use_cycles_debug and prefs.view.show_developer_ui:
_cycles.debug_flags_update(depsgraph.scene.as_pointer())
else:
_cycles.debug_flags_reset()
data = data.as_pointer()
depsgraph = depsgraph.as_pointer()
_cycles.reset(engine.session, data, depsgraph)
def sync(engine, depsgraph, data):
import _cycles
_cycles.sync(engine.session, depsgraph.as_pointer())
def view_draw(engine, depsgraph, region, v3d, rv3d):
import _cycles
depsgraph = depsgraph.as_pointer()
v3d = v3d.as_pointer()
rv3d = rv3d.as_pointer()
# draw render image
_cycles.view_draw(engine.session, depsgraph, v3d, rv3d)
def available_devices():
import _cycles
return _cycles.available_devices()
def with_osl():
import _cycles
return _cycles.with_osl
def osl_version():
import _cycles
return _cycles.osl_version
def with_path_guiding():
import _cycles
return _cycles.with_path_guiding
def system_info():
import _cycles
return _cycles.system_info()
def list_render_passes(scene, srl):
import _cycles
from bpy.app.translations import pgettext_n as n_
crl = srl.cycles
# Combined pass.
yield ("Combined", "RGBA", 'COLOR')
# Keep alignment for readability.
# autopep8: off
# Data passes.
if srl.use_pass_z: yield (n_("Depth"), "Z", 'VALUE')
if srl.use_pass_mist: yield (n_("Mist"), "Z", 'VALUE')
if srl.use_pass_position: yield (n_("Position"), "XYZ", 'VECTOR')
if srl.use_pass_normal: yield (n_("Normal"), "XYZ", 'VECTOR')
if srl.use_pass_vector: yield (n_("Vector"), "XYZW", 'VECTOR')
if srl.use_pass_uv: yield (n_("UV"), "UVA", 'VECTOR')
if srl.use_pass_object_index: yield (n_("Object Index"), "X", 'VALUE')
if srl.use_pass_material_index: yield (n_("Material Index"), "X", 'VALUE')
# Light passes.
if srl.use_pass_diffuse_direct: yield (n_("Diffuse Direct"), "RGB", 'COLOR')
if srl.use_pass_diffuse_indirect: yield (n_("Diffuse Indirect"), "RGB", 'COLOR')
if srl.use_pass_diffuse_color: yield (n_("Diffuse Color"), "RGB", 'COLOR')
if srl.use_pass_glossy_direct: yield (n_("Glossy Direct"), "RGB", 'COLOR')
if srl.use_pass_glossy_indirect: yield (n_("Glossy Indirect"), "RGB", 'COLOR')
if srl.use_pass_glossy_color: yield (n_("Glossy Color"), "RGB", 'COLOR')
if srl.use_pass_transmission_direct: yield (n_("Transmission Direct"), "RGB", 'COLOR')
if srl.use_pass_transmission_indirect: yield (n_("Transmission Indirect"), "RGB", 'COLOR')
if srl.use_pass_transmission_color: yield (n_("Transmission Color"), "RGB", 'COLOR')
if crl.use_pass_volume_direct: yield (n_("Volume Direct"), "RGB", 'COLOR')
if crl.use_pass_volume_indirect: yield (n_("Volume Indirect"), "RGB", 'COLOR')
if crl.use_pass_volume_scatter: yield (n_("Volume Scatter"), "RGB", 'COLOR')
if crl.use_pass_volume_transmit: yield (n_("Volume Transmit"), "RGB", 'COLOR')
if crl.use_pass_volume_majorant: yield (n_("Volume Majorant"), "Z", 'VALUE')
if srl.use_pass_emit: yield (n_("Emission"), "RGB", 'COLOR')
if srl.use_pass_environment: yield (n_("Environment"), "RGB", 'COLOR')
if srl.use_pass_ambient_occlusion: yield (n_("Ambient Occlusion"), "RGB", 'COLOR')
if crl.use_pass_shadow_catcher: yield (n_("Shadow Catcher"), "RGB", 'COLOR')
# autopep8: on
# Debug passes.
if crl.pass_debug_sample_count:
yield (n_("Debug Sample Count"), "X", 'VALUE')
if crl.pass_render_time:
# Only yield the pass if rendering on CPU
if scene.cycles.device == 'CPU':
yield (n_("Render Time"), "X", "VALUE")
# Cryptomatte passes.
# NOTE: Name channels are lowercase RGBA so that compression rules check in OpenEXR DWA code
# uses lossless compression. Reportedly this naming is the only one which works good from the
# interoperability point of view. Using XYZW naming is not portable.
crypto_depth = (min(16, srl.pass_cryptomatte_depth) + 1) // 2
if srl.use_pass_cryptomatte_object:
for i in range(0, crypto_depth):
yield ("CryptoObject" + '{:02d}'.format(i), "rgba", 'COLOR')
if srl.use_pass_cryptomatte_material:
for i in range(0, crypto_depth):
yield ("CryptoMaterial" + '{:02d}'.format(i), "rgba", 'COLOR')
if srl.use_pass_cryptomatte_asset:
for i in range(0, crypto_depth):
yield ("CryptoAsset" + '{:02d}'.format(i), "rgba", 'COLOR')
# Denoising passes.
if scene.cycles.use_denoising and crl.use_denoising:
yield (n_("Noisy Image"), "RGBA", 'COLOR')
if crl.use_pass_shadow_catcher:
yield (n_("Noisy Shadow Catcher"), "RGB", 'COLOR')
if crl.denoising_store_passes:
yield (n_("Denoising Albedo"), "RGB", 'COLOR')
yield (n_("Denoising Specular Albedo"), "RGB", 'COLOR')
yield (n_("Denoising Normal"), "XYZ", 'VECTOR')
yield (n_("Denoising Roughness"), "X", 'VALUE')
yield (n_("Denoising Depth"), "Z", 'VALUE')
# Custom AOV passes.
for aov in srl.aovs:
if not aov.is_valid:
continue
if aov.type == 'VALUE':
yield (aov.name, "X", 'VALUE')
else:
yield (aov.name, "RGBA", 'COLOR')
# Light groups.
for lightgroup in srl.lightgroups:
yield ("Combined_%s" % lightgroup.name, "RGB", 'COLOR')
# Path guiding debug passes.
if _cycles.with_debug and scene.cycles.use_guiding:
yield (n_("Guiding Color"), "RGB", 'COLOR')
yield (n_("Guiding Probability"), "X", 'VALUE')
yield (n_("Guiding Average Roughness"), "X", 'VALUE')
def register_passes(engine, scene, view_layer):
for name, channelids, channeltype in list_render_passes(scene, view_layer):
engine.register_pass(scene, view_layer, name, len(channelids), channelids, channeltype)

View File

@@ -0,0 +1,106 @@
# SPDX-FileCopyrightText: 2026 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0
import pathlib
import sys
def argparse_create():
import argparse
parser = argparse.ArgumentParser(
prog=pathlib.Path(sys.argv[0]).name + " --command maketx",
description=(
"Generate Cycles texture cache (.tx) files.\n"
"\n"
"When a file path is given, generate a tx file for that image.\n"
"When no file path is given, a blend file must be loaded and tx\n"
"files are generated for all images used by shader node trees."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"filepath",
type=str,
nargs="?",
default=None,
help="Source image file path. If omitted, generate for all images in the blend file.",
)
parser.add_argument(
"--colorspace",
dest="colorspace",
type=str,
default="auto",
help="Color space name. Auto-detected if omitted. Only used with a file path.",
)
parser.add_argument(
"--alpha-type",
dest="alpha_type",
type=str,
default="auto",
choices=["straight", "premultiplied", "channel_packed", "none", "auto"],
help="Alpha type. Auto-detected if omitted. Only used with a file path.",
)
parser.add_argument(
"--cache-dir",
dest="cache_dir",
type=str,
default="",
help="Output directory for tx files, relative to source image or absolute.",
)
return parser
def maketx_command(argv):
parser = argparse_create()
args = parser.parse_args(argv)
if args.filepath is not None:
return maketx_file(args)
else:
return maketx_blend(args, parser)
def maketx_file(args):
import _cycles
filepath = str(pathlib.Path(args.filepath).absolute())
try:
out_filepath = _cycles.maketx(
filepath,
colorspace=args.colorspace,
alpha_type=args.alpha_type,
cache_dir=args.cache_dir,
)
except RuntimeError as ex:
sys.stderr.write("Error: {:s}\n".format(str(ex)))
return 1
print(out_filepath)
return 0
def maketx_blend(args, parser):
import bpy
if not bpy.data.filepath:
parser.error("No image file path given and no blend file loaded.")
if args.colorspace != "auto":
parser.error("--colorspace is only used with an image file path argument.")
if args.alpha_type != "auto":
parser.error("--alpha-type is only used with an image file path argument.")
if args.cache_dir:
bpy.context.preferences.filepaths.texture_cache_directory = args.cache_dir
bpy.ops.render.generate_texture_cache()
return 0

View File

@@ -0,0 +1,167 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import bpy
from bpy.types import Operator
from bpy.props import StringProperty
from bpy.app.translations import pgettext_tip as tip_
class CYCLES_OT_use_shading_nodes(Operator):
"""Enable nodes on a light"""
bl_idname = "cycles.use_shading_nodes"
bl_label = "Use Nodes"
@classmethod
def poll(cls, context):
return getattr(context, "light", False)
def execute(self, context):
if context.light:
context.light.use_nodes = True
return {'FINISHED'}
class CYCLES_OT_denoise_animation(Operator):
"Denoise rendered animation sequence using current scene and view " \
"layer settings. Requires denoising data passes and output to " \
"OpenEXR multilayer files"
bl_idname = "cycles.denoise_animation"
bl_label = "Denoise Animation"
input_filepath: StringProperty(
name='Input Filepath',
description='File path for image to denoise. If not specified, uses the render file path and frame range from the scene',
default='',
subtype='FILE_PATH')
output_filepath: StringProperty(
name='Output Filepath',
description='If not specified, renders will be denoised in-place',
default='',
subtype='FILE_PATH')
def execute(self, context):
import os
preferences = context.preferences
scene = context.scene
view_layer = context.view_layer
in_filepath = self.input_filepath
out_filepath = self.output_filepath
in_filepaths = []
out_filepaths = []
if in_filepath != '':
# Denoise a single file
if out_filepath == '':
out_filepath = in_filepath
in_filepaths.append(in_filepath)
out_filepaths.append(out_filepath)
else:
# Denoise animation sequence with expanded frames matching
# Blender render output file naming.
in_filepath = scene.render.filepath
if out_filepath == '':
out_filepath = in_filepath
# Backup since we will overwrite the scene path temporarily
original_filepath = scene.render.filepath
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.render.filepath = in_filepath
filepath = scene.render.frame_path(frame=frame)
in_filepaths.append(filepath)
if not os.path.isfile(filepath):
scene.render.filepath = original_filepath
err_msg = tip_("Frame '%s' not found, animation must be complete") % filepath
self.report({'ERROR'}, err_msg)
return {'CANCELLED'}
scene.render.filepath = out_filepath
filepath = scene.render.frame_path(frame=frame)
out_filepaths.append(filepath)
scene.render.filepath = original_filepath
# Run denoiser
# TODO: support cancel and progress reports.
import _cycles
try:
_cycles.denoise(preferences.as_pointer(),
scene.as_pointer(),
view_layer.as_pointer(),
input=in_filepaths,
output=out_filepaths)
except Exception as e:
self.report({'ERROR'}, str(e))
return {'FINISHED'}
self.report({'INFO'}, "Denoising completed")
return {'FINISHED'}
class CYCLES_OT_merge_images(Operator):
"Combine OpenEXR multi-layer images rendered with different sample " \
"ranges into one image with reduced noise"
bl_idname = "cycles.merge_images"
bl_label = "Merge Images"
input_filepath1: StringProperty(
name='Input Filepath',
description='File path for image to merge',
default='',
subtype='FILE_PATH')
input_filepath2: StringProperty(
name='Input Filepath',
description='File path for image to merge',
default='',
subtype='FILE_PATH')
output_filepath: StringProperty(
name='Output Filepath',
description='File path for merged image',
default='',
subtype='FILE_PATH')
def execute(self, context):
in_filepaths = [self.input_filepath1, self.input_filepath2]
out_filepath = self.output_filepath
import _cycles
try:
_cycles.merge(input=in_filepaths, output=out_filepath)
except Exception as e:
self.report({'ERROR'}, str(e))
return {'FINISHED'}
return {'FINISHED'}
classes = (
CYCLES_OT_use_shading_nodes,
CYCLES_OT_denoise_animation,
CYCLES_OT_merge_images
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)

View File

@@ -0,0 +1,375 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import bpy
import _cycles
from bpy.app.translations import pgettext_rpt as rpt_
def osl_compile(input_path, report):
"""compile .osl file with given filepath to temporary .oso file"""
import tempfile
output_file = tempfile.NamedTemporaryFile(mode='w', suffix=".oso", delete=False)
output_path = output_file.name
output_file.close()
ok = _cycles.osl_compile(input_path, output_path)
if ok:
report({'INFO'}, "OSL shader compilation succeeded")
return ok, output_path
def shader_param_type_default(param, is_bool):
if param.isclosure:
return 'NodeSocketShader', None
elif param.type.vecsemantics == param.type.vecsemantics.COLOR:
return 'NodeSocketColor', (param.value[0], param.value[1], param.value[2], 1.0)
elif param.type.vecsemantics in [
param.type.vecsemantics.POINT,
param.type.vecsemantics.VECTOR,
param.type.vecsemantics.NORMAL,
]:
return 'NodeSocketVector', param.value
elif param.type.aggregate == param.type.aggregate.SCALAR:
if param.type.basetype == param.type.basetype.INT:
if is_bool:
return 'NodeSocketBool', bool(param.value)
else:
return 'NodeSocketInt', int(param.value)
elif param.type.basetype == param.type.basetype.FLOAT:
return 'NodeSocketFloat', float(param.value)
elif param.type.basetype == param.type.basetype.STRING:
return 'NodeSocketString', str(param.value)
return None, None
def shader_param_ensure(node, param):
# Skip unsupported types
if param.varlenarray or param.isstruct or param.type.arraylen > 1:
return None
metadata = {meta.name: meta.value for meta in param.metadata}
is_bool = metadata.get('widget') in ['boolean', 'checkBox']
hide_value = (param.value is None) or (metadata.get('widget') == 'null')
label = metadata.get('label', param.name)
socket_type, default = shader_param_type_default(param, is_bool)
if not socket_type:
return None
sockets = node.outputs if param.isoutput else node.inputs
if param.name in sockets:
sock = sockets[param.name]
if sock.bl_idname != socket_type:
# Type doesn't match, delete the socket and recreate it below
sockets.remove(sock)
else:
# Update properties if needed
if sock.name != label:
sock.name = label
if not param.isoutput and sock.hide_value != hide_value:
sock.hide_value = hide_value
# We have a matching socket, no need to create one
return sock
sock = sockets.new(type=socket_type, name=label, identifier=param.name)
if default is not None:
sock.default_value = default
sock.hide_value = hide_value
return sock
def osl_param_ensure_property(ccam, param):
import idprop
if param.isoutput or param.isclosure:
return None
# Get metadata for the parameter to control UI display
metadata = {meta.name: meta.value for meta in param.metadata}
if 'label' not in metadata:
metadata['label'] = param.name
datatype = None
if param.type.basetype == param.type.basetype.INT:
datatype = int
elif param.type.basetype == param.type.basetype.FLOAT:
datatype = float
elif param.type.basetype == param.type.basetype.STRING:
datatype = str
# OSl doesn't have boolean as a type, but we do
if (datatype == int) and (metadata.get('widget') in ('boolean', 'checkBox')):
datatype = bool
default = param.value if isinstance(param.value, tuple) else [param.value]
default = [datatype(v) for v in default]
name = param.name
if name in ccam:
# If the parameter already exists, only reset its value if its type
# or array length changed
cur_data = ccam[name]
if isinstance(cur_data, idprop.types.IDPropertyArray):
cur_length = len(cur_data)
cur_type = type(cur_data[0])
else:
cur_length = 1
cur_type = type(cur_data)
do_replace = datatype != cur_type or len(default) != cur_length
else:
# Parameter doesn't exist yet, so set it from the defaults
do_replace = True
if do_replace:
ccam[name] = tuple(default) if len(default) > 1 else default[0]
ui = ccam.id_properties_ui(name)
ui.clear()
ui.update(default=tuple(default) if len(default) > 1 else default[0])
# Determine subtype (limited unit support for now)
if param.type.vecsemantics == param.type.vecsemantics.COLOR:
ui.update(subtype='COLOR')
elif param.type.vecsemantics == param.type.vecsemantics.POINT:
ui.update(subtype='TRANSLATION')
elif param.type.vecsemantics == param.type.vecsemantics.NORMAL:
ui.update(subtype='DIRECTION')
elif datatype is str and metadata.get('widget') == 'filename':
ui.update(subtype='FILE_PATH')
elif datatype is float and metadata.get('unit') == 'radians':
ui.update(subtype='ANGLE')
elif datatype is float and metadata.get('unit') == 'm':
ui.update(subtype='DISTANCE')
elif datatype is float and metadata.get('unit') == 'mm':
ui.update(subtype='DISTANCE_CAMERA')
elif datatype is float and metadata.get('unit') in ('s', 'sec'):
ui.update(subtype='TIME_ABSOLUTE')
elif metadata.get('slider'):
ui.update(subtype='FACTOR')
elif datatype is int and metadata.get('widget') == 'mapper':
options = metadata.get('options', "")
options = options.split("|")
option_items = []
for option in options:
if ":" not in option:
continue
item, index = option.split(":")
# Ensure that the index can be converted to an integer
try:
int(index)
except ValueError:
continue
option_items.append((str(index), bpy.path.display_name(item), ""))
ui.update(items=option_items)
# Map OSL metadata to Blender names
option_map = {
'help': 'description',
'sensitivity': 'step', 'digits': 'precision',
'min': 'min', 'max': 'max',
'slidermin': 'soft_min', 'slidermax': 'soft_max',
}
if 'sensitivity' in metadata:
# Blender divides this value by 100 by convention, so counteract that.
metadata['sensitivity'] *= 100
for option, value in metadata.items():
if option in option_map:
ui.update(**{option_map[option]: value})
return name
def update_external_script(report, filepath, library):
"""compile and update OSL script"""
import os
import shutil
oso_file_remove = False
script_path = bpy.path.abspath(filepath, library=library)
script_path_noext, script_ext = os.path.splitext(script_path)
if script_ext == ".oso":
# it's a .oso file, no need to compile
ok, oso_path = True, script_path
elif script_ext == ".osl":
# compile .osl file
ok, oso_path = osl_compile(script_path, report)
oso_file_remove = True
if ok:
# copy .oso from temporary path to .osl directory
dst_path = script_path_noext + ".oso"
try:
shutil.copy2(oso_path, dst_path)
except:
report({'ERROR'}, rpt_("Failed to write .oso file next to external .osl file at {:s}").format(dst_path))
elif os.path.dirname(filepath) == "":
# module in search path
oso_path = filepath
ok = True
else:
# unknown
report({'ERROR'}, "External shader script must have .osl or .oso extension, or be a module name")
ok = False
return ok, oso_path, oso_file_remove
def update_internal_script(report, script):
"""compile and update shader script node"""
import os
import tempfile
import pathlib
import hashlib
bytecode = None
bytecode_hash = None
osl_path = bpy.path.abspath(script.filepath, library=script.library)
if script.is_in_memory or script.is_dirty or script.is_modified or not os.path.exists(osl_path):
# write text datablock contents to temporary file
osl_file = tempfile.NamedTemporaryFile(mode='w', suffix=".osl", delete=False)
osl_file.write(script.as_string())
osl_file.write("\n")
osl_file.close()
ok, oso_path = osl_compile(osl_file.name, report)
os.remove(osl_file.name)
else:
# compile text datablock from disk directly
ok, oso_path = osl_compile(osl_path, report)
if ok:
# read bytecode
try:
bytecode = pathlib.Path(oso_path).read_text()
md5 = hashlib.md5(usedforsecurity=False)
md5.update(bytecode.encode())
bytecode_hash = md5.hexdigest()
except:
import traceback
traceback.print_exc()
report({'ERROR'}, "Cannot read OSO bytecode to store in node at {!r}".format(oso_path))
ok = False
return ok, oso_path, bytecode, bytecode_hash
def update_script_node(node, report):
"""compile and update shader script node"""
import os
import oslquery
oso_file_remove = False
if node.mode == 'EXTERNAL':
# compile external script file
ok, oso_path, oso_file_remove = update_external_script(report, node.filepath, node.id_data.library)
if ok:
# Clear old internal bytecode, and also trigger node update if it was already cleared.
node.bytecode = ""
node.bytecode_hash = ""
elif node.mode == 'INTERNAL' and node.script:
# internal script, we will store bytecode in the node
ok, oso_path, bytecode, bytecode_hash = update_internal_script(report, node.script)
if bytecode:
node.bytecode = bytecode
node.bytecode_hash = bytecode_hash
else:
report({'WARNING'}, "No text or file specified in node, nothing to compile")
return
if ok:
if query := oslquery.OSLQuery(oso_path):
# Ensure that all parameters have a matching socket
used_sockets = set()
for param in query.parameters:
if sock := shader_param_ensure(node, param):
used_sockets.add(sock)
# Remove unused sockets
for sockets in (node.inputs, node.outputs):
for identifier in [sock.identifier for sock in sockets]:
if sockets[identifier] not in used_sockets:
sockets.remove(sockets[identifier])
else:
ok = False
report({'ERROR'}, rpt_("OSL query failed to open %s") % oso_path)
else:
report({'ERROR'}, "OSL script compilation failed, see console for errors")
# remove temporary oso file
if oso_file_remove:
try:
os.remove(oso_path)
except:
pass
return ok
def update_custom_camera_shader(cam, report):
"""compile and update custom camera shader"""
import os
import oslquery
oso_file_remove = False
custom_props = cam.cycles_custom
if cam.custom_mode == 'EXTERNAL':
# compile external script file
ok, oso_path, oso_file_remove = update_external_script(report, cam.custom_filepath, cam.library)
elif cam.custom_mode == 'INTERNAL' and cam.custom_shader:
# internal script, we will store bytecode in the node
ok, oso_path, bytecode, bytecode_hash = update_internal_script(report, cam.custom_shader)
if bytecode:
cam.custom_bytecode = bytecode
cam.custom_bytecode_hash = bytecode_hash
cam.update_tag()
else:
report({'WARNING'}, "No text or file specified in node, nothing to compile")
return
if ok:
if query := oslquery.OSLQuery(oso_path):
# Ensure that all parameters have a matching property
used_params = set()
for param in query.parameters:
if name := osl_param_ensure_property(custom_props, param):
used_params.add(name)
# Clean up unused parameters
for prop in list(custom_props.keys()):
if prop not in used_params:
del custom_props[prop]
else:
ok = False
report({'ERROR'}, rpt_("OSL query failed to open %s") % oso_path)
else:
report({'ERROR'}, "Custom Camera shader compilation failed, see console for errors")
# remove temporary oso file
if oso_file_remove:
try:
os.remove(oso_path)
except:
pass
return ok

View File

@@ -0,0 +1,136 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
from bl_operators.presets import AddPresetBase
from bpy.types import Operator
class AddPresetIntegrator(AddPresetBase, Operator):
'''Add an Integrator Preset'''
bl_idname = "render.cycles_integrator_preset_add"
bl_label = "Add Integrator Preset"
preset_menu = "CYCLES_PT_integrator_presets"
preset_defines = [
"cycles = bpy.context.scene.cycles"
]
preset_values = [
"cycles.max_bounces",
"cycles.diffuse_bounces",
"cycles.glossy_bounces",
"cycles.transmission_bounces",
"cycles.volume_bounces",
"cycles.transparent_max_bounces",
"cycles.caustics_reflective",
"cycles.caustics_refractive",
"cycles.blur_glossy",
"cycles.use_fast_gi",
"cycles.ao_bounces",
"cycles.ao_bounces_render",
]
preset_subdir = "cycles/integrator"
class AddPresetSampling(AddPresetBase, Operator):
'''Add a Sampling Preset'''
bl_idname = "render.cycles_sampling_preset_add"
bl_label = "Add Sampling Preset"
preset_menu = "CYCLES_PT_sampling_presets"
preset_defines = [
"cycles = bpy.context.scene.cycles"
]
preset_values = [
"cycles.use_adaptive_sampling",
"cycles.samples",
"cycles.adaptive_threshold",
"cycles.adaptive_min_samples",
"cycles.time_limit",
"cycles.use_denoising",
"cycles.denoiser",
"cycles.denoising_input_passes",
"cycles.denoising_prefilter",
"cycles.denoising_quality",
]
preset_subdir = "cycles/sampling"
class AddPresetViewportSampling(AddPresetBase, Operator):
'''Add a Viewport Sampling Preset'''
bl_idname = "render.cycles_viewport_sampling_preset_add"
bl_label = "Add Viewport Sampling Preset"
preset_menu = "CYCLES_PT_viewport_sampling_presets"
preset_defines = [
"cycles = bpy.context.scene.cycles"
]
preset_values = [
"cycles.use_preview_adaptive_sampling",
"cycles.preview_samples",
"cycles.preview_adaptive_threshold",
"cycles.preview_adaptive_min_samples",
"cycles.use_preview_denoising",
"cycles.preview_denoiser",
"cycles.preview_denoising_input_passes",
"cycles.preview_denoising_prefilter",
"cycles.preview_denoising_quality",
"cycles.preview_denoising_start_sample",
]
preset_subdir = "cycles/viewport_sampling"
class AddPresetPerformance(AddPresetBase, Operator):
'''Add an Performance Preset'''
bl_idname = "render.cycles_performance_preset_add"
bl_label = "Add Performance Preset"
preset_menu = "CYCLES_PT_performance_presets"
preset_defines = [
"render = bpy.context.scene.render",
"cycles = bpy.context.scene.cycles",
]
preset_values = [
"render.threads_mode",
"render.use_persistent_data",
"cycles.debug_use_spatial_splits",
"cycles.debug_use_compact_bvh",
"cycles.debug_use_hair_bvh",
"cycles.debug_bvh_time_steps",
"cycles.tile_size",
]
preset_subdir = "cycles/performance"
classes = (
AddPresetIntegrator,
AddPresetSampling,
AddPresetViewportSampling,
AddPresetPerformance,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
if __name__ == "__main__":
register()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,330 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import bpy
from bpy.app.handlers import persistent
def custom_bake_remap(scene):
"""
Remap bake types into the new types and set the flags accordingly
"""
bake_lookup = (
'COMBINED',
'AO',
'SHADOW',
'NORMAL',
'UV',
'EMIT',
'ENVIRONMENT',
'DIFFUSE_DIRECT',
'DIFFUSE_INDIRECT',
'DIFFUSE_COLOR',
'GLOSSY_DIRECT',
'GLOSSY_INDIRECT',
'GLOSSY_COLOR',
'TRANSMISSION_DIRECT',
'TRANSMISSION_INDIRECT',
'TRANSMISSION_COLOR')
diffuse_direct_idx = bake_lookup.index('DIFFUSE_DIRECT')
cscene = scene.cycles
# Old bake type
bake_type_idx = cscene.get("bake_type")
if bake_type_idx is None:
cscene.bake_type = 'COMBINED'
return
# File doesn't need versioning
if bake_type_idx < diffuse_direct_idx:
return
# File needs versioning
bake_type = bake_lookup[bake_type_idx]
cscene.bake_type, end = bake_type.split('_')
if end == 'DIRECT':
scene.render.bake.use_pass_indirect = False
scene.render.bake.use_pass_color = False
elif end == 'INDIRECT':
scene.render.bake.use_pass_direct = False
scene.render.bake.use_pass_color = False
elif end == 'COLOR':
scene.render.bake.use_pass_direct = False
scene.render.bake.use_pass_indirect = False
@persistent
def do_versions(self):
if bpy.context.preferences.version <= (2, 78, 1):
prop = bpy.context.preferences.addons[__package__].preferences
system = bpy.context.preferences.system
if not prop.is_property_set("compute_device_type"):
# Device might not currently be available so this can fail
try:
if system.legacy_compute_device_type == 1:
prop.compute_device_type = 'NONE' # Was OpenCL
elif system.legacy_compute_device_type == 2:
prop.compute_device_type = 'CUDA'
else:
prop.compute_device_type = 'NONE'
except:
pass
# Init device list for UI
prop.get_devices(prop.compute_device_type)
if bpy.context.preferences.version <= (3, 0, 40):
# Disable OpenCL device
prop = bpy.context.preferences.addons[__package__].preferences
if prop.is_property_set("compute_device_type") and prop['compute_device_type'] == 4:
prop.compute_device_type = 'NONE'
# We don't modify startup file because it assumes to
# have all the default values only.
if not bpy.data.is_saved:
return
# Map of versions used by libraries.
library_versions = {}
library_versions[bpy.data.version] = [None]
for library in bpy.data.libraries:
library_versions.setdefault(library.version, []).append(library)
# Do versioning per library, since they might have different versions.
max_need_versioning = (5, 2, 8)
for version, libraries in library_versions.items():
if version > max_need_versioning:
continue
# Scenes
for scene in bpy.data.scenes:
if scene.library not in libraries:
continue
# Auto tiling is always enabled now
if version <= (5, 0, 77):
cscene = scene.cycles
if not cscene.use_auto_tile:
cscene.use_auto_tile = True
cscene.tile_size = 8192
# Clamp Direct/Indirect separation in 270
if version <= (2, 70, 0):
cscene = scene.cycles
sample_clamp = cscene.get("sample_clamp", False)
if (sample_clamp and
not cscene.is_property_set("sample_clamp_direct") and
not cscene.is_property_set("sample_clamp_indirect")):
cscene.sample_clamp_direct = sample_clamp
cscene.sample_clamp_indirect = sample_clamp
# Change of Volume Bounces in 271
if version <= (2, 71, 0):
cscene = scene.cycles
if not cscene.is_property_set("volume_bounces"):
cscene.volume_bounces = 1
# Caustics Reflective/Refractive separation in 272
if version <= (2, 72, 0):
cscene = scene.cycles
if (
cscene.get("no_caustics", False) and
not cscene.is_property_set("caustics_reflective") and
not cscene.is_property_set("caustics_refractive")
):
cscene.caustics_reflective = False
cscene.caustics_refractive = False
# Baking types changed
if version <= (2, 76, 6):
custom_bake_remap(scene)
# Several default changes for 2.77
if version <= (2, 76, 8):
cscene = scene.cycles
# Samples
if not cscene.is_property_set("samples"):
cscene.samples = 10
# Preview Samples
if not cscene.is_property_set("preview_samples"):
cscene.preview_samples = 10
# Filter
if cscene.get("filter_type", -1) == -1:
cscene.pixel_filter_type = 'GAUSSIAN'
if version <= (2, 76, 10):
cscene = scene.cycles
if not cscene.is_property_set("pixel_filter_type"):
filter_type_int = cscene.get("filter_type", -1)
if filter_type_int == 0:
cscene.pixel_filter_type = 'BOX'
elif filter_type_int == 1:
cscene.pixel_filter_type = 'GAUSSIAN'
if version <= (2, 78, 2):
cscene = scene.cycles
if not cscene.is_property_set("light_sampling_threshold"):
cscene.light_sampling_threshold = 0.0
if version <= (2, 79, 0):
cscene = scene.cycles
# Default changes
if not cscene.is_property_set("blur_glossy"):
cscene.blur_glossy = 0.0
if not cscene.is_property_set("sample_clamp_indirect"):
cscene.sample_clamp_indirect = 0.0
if version <= (2, 92, 4):
if scene.render.engine == 'CYCLES':
for view_layer in scene.view_layers:
cview_layer = view_layer.cycles
view_layer.use_pass_cryptomatte_object = cview_layer.get("use_pass_crypto_object", False)
view_layer.use_pass_cryptomatte_material = cview_layer.get("use_pass_crypto_material", False)
view_layer.use_pass_cryptomatte_asset = cview_layer.get("use_pass_crypto_asset", False)
view_layer.pass_cryptomatte_depth = cview_layer.get("pass_crypto_depth", 6)
if version <= (2, 93, 7):
if scene.render.engine == 'CYCLES':
for view_layer in scene.view_layers:
cview_layer = view_layer.cycles
for caov in cview_layer.get("aovs", []):
aov_name = caov.get("name", "AOV")
if aov_name in view_layer.aovs:
continue
baov = view_layer.aovs.add()
baov.name = caov.get("name", "AOV")
baov.type = "COLOR" if caov.get("type", 1) == 1 else "VALUE"
if version <= (2, 93, 16):
cscene = scene.cycles
ao_bounces = cscene.get("ao_bounces", 0)
ao_bounces_render = cscene.get("ao_bounces_render", 0)
if scene.render.use_simplify and (ao_bounces or ao_bounces_render):
cscene.use_fast_gi = True
cscene.ao_bounces = ao_bounces
cscene.ao_bounces_render = ao_bounces_render
else:
cscene.ao_bounces = 1
cscene.ao_bounces_render = 1
if version <= (3, 0, 25):
cscene = scene.cycles
# Default changes.
if not cscene.is_property_set("samples"):
cscene.samples = 128
if not cscene.is_property_set("preview_samples"):
cscene.preview_samples = 32
if not cscene.is_property_set("use_adaptive_sampling"):
cscene.use_adaptive_sampling = False
cscene.use_preview_adaptive_sampling = False
if not cscene.is_property_set("use_denoising"):
cscene.use_denoising = False
if not cscene.is_property_set("use_preview_denoising"):
cscene.use_preview_denoising = False
if not cscene.is_property_set("sampling_pattern") or \
cscene.get('sampling_pattern') >= 2:
cscene.sampling_pattern = 'TABULATED_SOBOL'
# Removal of square samples.
cscene = scene.cycles
use_square_samples = cscene.get("use_square_samples", False)
if use_square_samples:
cscene.samples *= cscene.samples
cscene.preview_samples *= cscene.preview_samples
for layer in scene.view_layers:
layer.samples *= layer.samples
cscene["use_square_samples"] = False
if version <= (3, 5, 3):
cscene = scene.cycles
# Disable light tree for existing scenes.
if not cscene.is_property_set("use_light_tree"):
cscene.use_light_tree = False
# Sampling pattern settings are hidden behind a debug menu. Switch to the
# default faster and fully featured (Supports Scrambling Distance)
# Tabulated Sobol.
cscene.sampling_pattern = 'TABULATED_SOBOL'
if version <= (4, 2, 52):
cscene = scene.cycles
# Previous versions defaulted to Tabulated Sobol unless debugging options
# were enabled, so keep this behavior instead of suddenly defaulting to
# blue noise if the file happens to contain a different option for the enum.
cscene.sampling_pattern = 'TABULATED_SOBOL'
# Lamps
for light in bpy.data.lights:
if light.library not in libraries:
continue
if version <= (2, 76, 5):
clight = light.cycles
# MIS
if not clight.is_property_set("use_multiple_importance_sampling"):
clight.use_multiple_importance_sampling = False
# Worlds
for world in bpy.data.worlds:
if world.library not in libraries:
continue
if version <= (2, 76, 9):
cworld = world.cycles
# World MIS Resolution
if not cworld.is_property_set("sample_map_resolution"):
cworld.sample_map_resolution = 256
if version <= (2, 79, 4) or \
(version >= (2, 80, 0) and version <= (2, 80, 18)):
cworld = world.cycles
# World MIS
if not cworld.is_property_set("sampling_method"):
if cworld.get("sample_as_light", True):
cworld.sampling_method = 'MANUAL'
else:
cworld.sampling_method = 'NONE'
# Materials
for mat in bpy.data.materials:
if mat.library not in libraries:
continue
if version <= (2, 76, 5):
cmat = mat.cycles
# Volume Sampling
if not cmat.is_property_set("volume_sampling"):
cmat.volume_sampling = 'DISTANCE'
if version <= (2, 79, 2):
cmat = mat.cycles
if cmat.get("displacement_method", -1) == -1:
cmat['displacement_method'] = 0
# Change default to bump again.
if version <= (2, 79, 6) or \
(version >= (2, 80, 0) and version <= (2, 80, 41)):
cmat = mat.cycles
if cmat.get("displacement_method", -1) == -1:
cmat['displacement_method'] = 1
if version <= (3, 5, 3):
cmat = mat.cycles
if not cmat.get("sample_as_light", True):
cmat.emission_sampling = 'NONE'

View File

@@ -0,0 +1,188 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "scene/attribute.h"
#include "util/color.h"
#include "util/param.h"
#include "util/types.h"
#include "BKE_attribute.hh"
#include "BLI_color_types.hh"
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector_types.hh"
CCL_NAMESPACE_BEGIN
template<typename BlenderT> struct AttributeConverter {
using CyclesT = void;
};
template<> struct AttributeConverter<float> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = true;
static CyclesT convert(const float &value)
{
return value;
}
};
template<> struct AttributeConverter<int> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = false;
static CyclesT convert(const int &value)
{
return float(value);
}
};
template<> struct AttributeConverter<blender::float2> {
using CyclesT = float2;
static constexpr auto type_desc = TypeFloat2;
static constexpr bool layout_compatible = true;
static CyclesT convert(const blender::float2 &value)
{
return make_float2(value[0], value[1]);
}
};
template<> struct AttributeConverter<blender::float3> {
using CyclesT = packed_float3;
static constexpr auto type_desc = TypeVector;
static constexpr bool layout_compatible = true;
static CyclesT convert(const blender::float3 &value)
{
return packed_float3(make_float3(value[0], value[1], value[2]));
}
};
template<> struct AttributeConverter<blender::float4> {
using CyclesT = float4;
static constexpr auto type_desc = TypeFloat4;
/* Allocation alignment is not compatible with Cycles */
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::float4 &value)
{
return make_float4(value[0], value[1], value[2], value[3]);
}
};
template<> struct AttributeConverter<blender::ColorGeometry4f> {
using CyclesT = float4;
static constexpr auto type_desc = TypeRGBA;
/* Allocation alignment is not compatible with Cycles */
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::ColorGeometry4f &value)
{
return make_float4(value[0], value[1], value[2], value[3]);
}
};
template<> struct AttributeConverter<blender::ColorGeometry4b> {
using CyclesT = float4;
static constexpr auto type_desc = TypeRGBA;
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::ColorGeometry4b &value)
{
return color_srgb_to_linear_v4(make_float4(byte_to_float(value[0]),
byte_to_float(value[1]),
byte_to_float(value[2]),
byte_to_float(value[3])));
}
};
template<> struct AttributeConverter<bool> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = false;
static CyclesT convert(const bool &value)
{
return float(value);
}
};
template<> struct AttributeConverter<int8_t> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = false;
static CyclesT convert(const int8_t &value)
{
return float(value);
}
};
template<> struct AttributeConverter<blender::math::Quaternion> {
using CyclesT = float4;
static constexpr auto type_desc = TypeFloat4;
/* Allocation alignment is not compatible with Cycles */
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::math::Quaternion &value)
{
return make_float4(value.w, value.x, value.y, value.z);
}
};
/* Add a standard attribute from a Blender attribute reader, sharing the buffer
* with Blender when possible. */
template<typename BlenderT>
bool sync_attribute_from_blender(AttributeSet &attributes,
const AttributeStandard std,
const blender::bke::AttributeReader<BlenderT> &b_reader,
const int size)
{
if (!b_reader) {
return false;
}
using Converter = AttributeConverter<BlenderT>;
using CyclesT = typename Converter::CyclesT;
/* Try implicit sharing. */
if constexpr (Converter::layout_compatible) {
const blender::CommonVArrayInfo info = b_reader.varray.common_info();
if (info.type == blender::CommonVArrayInfo::Type::Span && b_reader.sharing_info) {
attributes.add_shared(std, ustring(), info.data, size, b_reader.sharing_info);
return true;
}
}
/* Otherwise allocate and copy. */
Attribute *attr = attributes.add(std);
CyclesT *data = attr->data_for_write<CyclesT>();
const blender::VArraySpan<BlenderT> src = *b_reader;
for (const int i : src.index_range()) {
data[i] = Converter::convert(src[i]);
}
return true;
}
/* Same as sync_attribute_from_blender, but for a single motion step of an
* existing attribute. */
template<typename BlenderT>
bool sync_attribute_motion_step_from_blender(
Attribute &attr,
const int motion_step,
const blender::bke::AttributeReader<BlenderT> &b_reader)
{
if (!b_reader) {
return false;
}
using Converter = AttributeConverter<BlenderT>;
using CyclesT = typename Converter::CyclesT;
/* Try implicit sharing. */
if constexpr (Converter::layout_compatible) {
const blender::CommonVArrayInfo info = b_reader.varray.common_info();
if (info.type == blender::CommonVArrayInfo::Type::Span && b_reader.sharing_info) {
attr.set_motion_step_shared(
motion_step, info.data, b_reader.varray.size(), b_reader.sharing_info);
return true;
}
}
/* Otherwise allocate and copy. */
CyclesT *data = attr.data_for_write<CyclesT>(motion_step);
const blender::VArraySpan<BlenderT> src = *b_reader;
for (const int i : src.index_range()) {
data[i] = Converter::convert(src[i]);
}
return true;
}
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "blender/device.h"
#include "blender/session.h"
#include "blender/util.h"
#include "BKE_scene.hh"
#include "RNA_prototypes.hh"
CCL_NAMESPACE_BEGIN
enum ComputeDevice {
COMPUTE_DEVICE_CPU = 0,
COMPUTE_DEVICE_CUDA = 1,
COMPUTE_DEVICE_OPTIX = 3,
COMPUTE_DEVICE_HIP = 4,
COMPUTE_DEVICE_METAL = 5,
COMPUTE_DEVICE_ONEAPI = 6,
COMPUTE_DEVICE_NUM
};
int blender_device_threads(blender::Scene &b_scene)
{
blender::RenderData &b_r = b_scene.r;
int threads_override = blender::BLI_system_num_threads_override_get();
if (threads_override > 0 || (b_r.mode & blender::R_FIXED_THREADS) != 0) {
return BKE_render_num_threads(&b_r);
}
return 0;
}
static void adjust_device_info_from_preferences(DeviceInfo &info, blender::PointerRNA cpreferences)
{
if (!get_boolean(cpreferences, "peer_memory")) {
info.has_peer_memory = false;
}
if (info.type == DEVICE_METAL) {
const MetalRTSetting use_metalrt = (MetalRTSetting)get_enum(
cpreferences, "metalrt", METALRT_NUM_SETTINGS, METALRT_AUTO);
info.use_hardware_raytracing = info.use_metalrt_by_default;
if (use_metalrt == METALRT_OFF) {
info.use_hardware_raytracing = false;
}
else if (use_metalrt == METALRT_ON) {
info.use_hardware_raytracing = true;
}
}
if (info.type == DEVICE_ONEAPI && !get_boolean(cpreferences, "use_oneapirt")) {
info.use_hardware_raytracing = false;
}
if (info.type == DEVICE_HIP && !get_boolean(cpreferences, "use_hiprt")) {
info.use_hardware_raytracing = false;
}
}
static void adjust_device_info(DeviceInfo &device, blender::PointerRNA cpreferences, bool preview)
{
adjust_device_info_from_preferences(device, cpreferences);
for (DeviceInfo &info : device.multi_devices) {
adjust_device_info_from_preferences(info, cpreferences);
}
if (preview) {
/* Disable specialization for preview renders. */
device.kernel_optimization_level = KERNEL_OPTIMIZATION_LEVEL_OFF;
}
else {
device.kernel_optimization_level = (KernelOptimizationLevel)get_enum(
cpreferences,
"kernel_optimization_level",
KERNEL_OPTIMIZATION_NUM_LEVELS,
KERNEL_OPTIMIZATION_LEVEL_FULL);
}
}
DeviceInfo blender_device_info(blender::UserDef &b_preferences,
blender::Scene &b_scene,
bool background,
bool preview,
DeviceInfo &preferences_device)
{
blender::PointerRNA scene_rna_ptr = RNA_id_pointer_create(&b_scene.id);
blender::PointerRNA cscene = RNA_pointer_get(&scene_rna_ptr, "cycles");
/* Find cycles preferences. */
blender::PointerRNA cpreferences;
for (blender::bAddon &b_addon : b_preferences.addons) {
if (STREQ(b_addon.module, "cycles")) {
blender::PointerRNA addon_rna_ptr = RNA_pointer_create_discrete(
nullptr, blender::RNA_Addon, &b_addon);
cpreferences = RNA_pointer_get(&addon_rna_ptr, "preferences");
break;
}
}
/* Default to CPU device. */
DeviceInfo cpu_device = Device::available_devices(DEVICE_MASK_CPU).front();
/* Device, which is chosen in the Blender Preferences. Default to CPU device. */
preferences_device = cpu_device;
/* Test if we are using GPU devices. */
const ComputeDevice compute_device = (ComputeDevice)get_enum(
cpreferences, "compute_device_type", COMPUTE_DEVICE_NUM, COMPUTE_DEVICE_CPU);
if (compute_device != COMPUTE_DEVICE_CPU) {
/* Query GPU devices with matching types. */
uint mask = DEVICE_MASK_CPU;
if (compute_device == COMPUTE_DEVICE_CUDA) {
mask |= DEVICE_MASK_CUDA;
}
else if (compute_device == COMPUTE_DEVICE_OPTIX) {
mask |= DEVICE_MASK_OPTIX;
}
else if (compute_device == COMPUTE_DEVICE_HIP) {
mask |= DEVICE_MASK_HIP;
}
else if (compute_device == COMPUTE_DEVICE_METAL) {
mask |= DEVICE_MASK_METAL;
}
else if (compute_device == COMPUTE_DEVICE_ONEAPI) {
mask |= DEVICE_MASK_ONEAPI;
}
const vector<DeviceInfo> devices = Device::available_devices(mask);
/* Match device preferences and available devices. */
vector<DeviceInfo> used_devices;
blender::CollectionPropertyIterator rna_iter;
for (RNA_collection_begin(&cpreferences, "devices", &rna_iter); rna_iter.valid;
RNA_property_collection_next(&rna_iter))
{
blender::PointerRNA device = rna_iter.ptr;
if (get_boolean(device, "use")) {
const string id = get_string(device, "id");
for (const DeviceInfo &info : devices) {
if (info.id == id) {
used_devices.push_back(info);
break;
}
}
}
}
blender::RNA_property_collection_end(&rna_iter);
if (!used_devices.empty()) {
const int threads = blender_device_threads(b_scene);
preferences_device = Device::get_multi_device(used_devices, threads, background);
}
}
adjust_device_info(preferences_device, cpreferences, preview);
adjust_device_info(cpu_device, cpreferences, preview);
/* Device, which will be used, according to Settings, Scene preferences and command line
* parameters. */
DeviceInfo device;
if (BlenderSession::device_override != DEVICE_MASK_ALL) {
const vector<DeviceInfo> devices = Device::available_devices(BlenderSession::device_override);
if (devices.empty()) {
device = Device::dummy_device("Found no Cycles device of the specified type");
}
else {
const int threads = blender_device_threads(b_scene);
device = Device::get_multi_device(devices, threads, background);
}
adjust_device_info(device, cpreferences, preview);
}
else {
/* 1 is a "GPU compute" in properties.py for Scene settings. */
if (get_enum(cscene, "device") == 1) {
device = preferences_device;
}
else {
device = cpu_device;
}
}
return device;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
namespace blender {
struct Scene;
struct UserDef;
} // namespace blender
#include "device/device.h"
CCL_NAMESPACE_BEGIN
/* Get number of threads to use for rendering. */
int blender_device_threads(blender::Scene &b_scene);
/* Convert Blender settings to device specification. In addition, preferences_device contains the
* device chosen in Cycles global preferences, which is useful for the denoiser device selection.
*/
DeviceInfo blender_device_info(blender::UserDef &b_preferences,
blender::Scene &b_scene,
bool background,
bool preview,
DeviceInfo &preferences_device);
CCL_NAMESPACE_END

View File

@@ -0,0 +1,907 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "GPU_context.hh"
#include "GPU_immediate.hh"
#include "GPU_platform.hh"
#include "GPU_platform_backend_enum.h"
#include "GPU_shader.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "RE_engine.h"
#include "blender/display_driver.h"
#include "util/log.h"
#include "util/math.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
/* --------------------------------------------------------------------
* BlenderDisplayShader.
*/
unique_ptr<BlenderDisplayShader> BlenderDisplayShader::create(blender::RenderEngine &b_engine,
blender::Scene &b_scene)
{
/* See #engine_support_display_space_shader in rna_render.cc. */
return make_unique<BlenderDisplaySpaceShader>(b_engine, b_scene);
}
int BlenderDisplayShader::get_position_attrib_location()
{
if (position_attribute_location_ == -1) {
blender::gpu::Shader *shader_program = get_shader_program();
position_attribute_location_ = blender::GPU_shader_get_attribute(shader_program,
position_attribute_name);
}
return position_attribute_location_;
}
int BlenderDisplayShader::get_tex_coord_attrib_location()
{
if (tex_coord_attribute_location_ == -1) {
blender::gpu::Shader *shader_program = get_shader_program();
tex_coord_attribute_location_ = blender::GPU_shader_get_attribute(shader_program,
tex_coord_attribute_name);
}
return tex_coord_attribute_location_;
}
/* --------------------------------------------------------------------
* BlenderDisplaySpaceShader.
*/
BlenderDisplaySpaceShader::BlenderDisplaySpaceShader(blender::RenderEngine &b_engine,
blender::Scene &b_scene)
: b_engine_(b_engine), b_scene_(b_scene)
{
}
blender::gpu::Shader *BlenderDisplaySpaceShader::bind(int /*width*/, int /*height*/)
{
blender::gpu::Shader *shader = blender::GPU_shader_get_builtin_shader(
blender::GPU_SHADER_3D_IMAGE);
blender::GPU_shader_bind(shader);
/** \note "image" binding slot is 0. */
return blender::GPU_shader_get_bound();
}
void BlenderDisplaySpaceShader::unbind()
{
blender::GPU_shader_unbind();
}
blender::gpu::Shader *BlenderDisplaySpaceShader::get_shader_program()
{
if (!shader_program_) {
shader_program_ = blender::GPU_shader_get_bound();
}
if (!shader_program_) {
LOG_ERROR << "Error retrieving shader program for display space shader.";
}
return shader_program_;
}
/* --------------------------------------------------------------------
* DrawTile.
*/
/* Higher level representation of a texture from the graphics library. */
class DisplayGPUTexture {
public:
/* Global counter for all allocated blender::GPUTextures used by instances of this class. */
static inline std::atomic<int> num_used = 0;
DisplayGPUTexture() = default;
~DisplayGPUTexture()
{
assert(gpu_texture == nullptr);
}
DisplayGPUTexture(const DisplayGPUTexture &other) = delete;
DisplayGPUTexture &operator=(DisplayGPUTexture &other) = delete;
DisplayGPUTexture(DisplayGPUTexture &&other) noexcept
: gpu_texture(other.gpu_texture), width(other.width), height(other.height)
{
other.reset();
}
DisplayGPUTexture &operator=(DisplayGPUTexture &&other)
{
if (this == &other) {
return *this;
}
gpu_texture = other.gpu_texture;
width = other.width;
height = other.height;
other.reset();
return *this;
}
bool gpu_resources_ensure(const uint texture_width, const uint texture_height)
{
if (width != texture_width || height != texture_height) {
gpu_resources_destroy();
}
if (gpu_texture) {
return true;
}
width = texture_width;
height = texture_height;
/* Texture must have a minimum size of 1x1. */
gpu_texture = blender::GPU_texture_create_2d("CyclesBlitTexture",
max(width, 1),
max(height, 1),
1,
blender::gpu::TextureFormat::SFLOAT_16_16_16_16,
blender::GPU_TEXTURE_USAGE_GENERAL,
nullptr);
if (!gpu_texture) {
LOG_ERROR << "Error creating texture.";
return false;
}
blender::GPU_texture_filter_mode(gpu_texture, false);
blender::GPU_texture_extend_mode(gpu_texture, blender::GPU_SAMPLER_EXTEND_MODE_EXTEND);
++num_used;
return true;
}
void gpu_resources_destroy()
{
if (gpu_texture == nullptr) {
return;
}
GPU_TEXTURE_FREE_SAFE(gpu_texture);
reset();
--num_used;
}
/* Texture resource allocated by the blender::GPU module.
*
* NOTE: Allocated on the render engine's context. */
blender::gpu::Texture *gpu_texture = nullptr;
/* Dimensions of the texture in pixels. */
int width = 0;
int height = 0;
protected:
void reset()
{
gpu_texture = nullptr;
width = 0;
height = 0;
}
};
/* Higher level representation of a Pixel Buffer Object (PBO) from the graphics library. */
class DisplayGPUPixelBuffer {
public:
/* Global counter for all allocated blender::GPU module PBOs used by instances of this class. */
static inline std::atomic<int> num_used = 0;
DisplayGPUPixelBuffer() = default;
~DisplayGPUPixelBuffer()
{
assert(gpu_pixel_buffer == nullptr);
}
DisplayGPUPixelBuffer(const DisplayGPUPixelBuffer &other) = delete;
DisplayGPUPixelBuffer &operator=(DisplayGPUPixelBuffer &other) = delete;
DisplayGPUPixelBuffer(DisplayGPUPixelBuffer &&other) noexcept
: gpu_pixel_buffer(other.gpu_pixel_buffer), width(other.width), height(other.height)
{
other.reset();
}
DisplayGPUPixelBuffer &operator=(DisplayGPUPixelBuffer &&other)
{
if (this == &other) {
return *this;
}
gpu_pixel_buffer = other.gpu_pixel_buffer;
width = other.width;
height = other.height;
other.reset();
return *this;
}
bool gpu_resources_ensure(const uint new_width, const uint new_height, bool &buffer_recreated)
{
buffer_recreated = false;
const size_t required_size = sizeof(half4) * new_width * new_height;
/* Try to re-use the existing PBO if it has usable size. */
if (gpu_pixel_buffer) {
if (new_width != width || new_height != height ||
blender::GPU_pixel_buffer_size(gpu_pixel_buffer) < required_size)
{
buffer_recreated = true;
gpu_resources_destroy();
}
}
/* Update size. */
width = new_width;
height = new_height;
/* Create pixel buffer if not already created. */
if (!gpu_pixel_buffer) {
gpu_pixel_buffer = blender::GPU_pixel_buffer_create(required_size);
buffer_recreated = true;
}
if (gpu_pixel_buffer == nullptr) {
LOG_ERROR << "Error creating texture pixel buffer object.";
return false;
}
++num_used;
return true;
}
void gpu_resources_destroy()
{
if (!gpu_pixel_buffer) {
return;
}
blender::GPU_pixel_buffer_free(gpu_pixel_buffer);
gpu_pixel_buffer = nullptr;
reset();
--num_used;
}
/* Pixel Buffer Object allocated by the blender::GPU module.
*
* NOTE: Allocated on the render engine's context. */
blender::GPUPixelBuffer *gpu_pixel_buffer = nullptr;
/* Dimensions of the PBO. */
int width = 0;
int height = 0;
protected:
void reset()
{
gpu_pixel_buffer = nullptr;
width = 0;
height = 0;
}
};
class DrawTile {
public:
DrawTile() = default;
~DrawTile() = default;
DrawTile(const DrawTile &other) = delete;
DrawTile &operator=(const DrawTile &other) = delete;
DrawTile(DrawTile &&other) noexcept = default;
DrawTile &operator=(DrawTile &&other) = default;
void gpu_resources_destroy()
{
texture.gpu_resources_destroy();
}
bool ready_to_draw() const
{
return texture.gpu_texture != nullptr;
}
/* Texture which contains pixels of the tile. */
DisplayGPUTexture texture;
/* Display parameters the texture of this tile has been updated for. */
BlenderDisplayDriver::Params params;
};
class DrawTileAndPBO {
public:
void gpu_resources_destroy()
{
tile.gpu_resources_destroy();
buffer_object.gpu_resources_destroy();
}
DrawTile tile;
DisplayGPUPixelBuffer buffer_object;
bool need_update_texture_pixels = false;
};
/* --------------------------------------------------------------------
* BlenderDisplayDriver.
*/
struct BlenderDisplayDriver::Tiles {
/* Resources of a tile which is being currently rendered. */
DrawTileAndPBO current_tile;
/* All tiles which rendering is finished and which content will not be changed. */
struct {
vector<DrawTile> tiles;
void gl_resources_destroy_and_clear()
{
for (DrawTile &tile : tiles) {
tile.gpu_resources_destroy();
}
tiles.clear();
}
} finished_tiles;
};
BlenderDisplayDriver::BlenderDisplayDriver(blender::RenderEngine &b_engine,
blender::Scene &b_scene,
blender::RegionView3D *b_rv3d,
const bool background)
: b_engine_(b_engine),
b_rv3d_(b_rv3d),
background_(background),
display_shader_(BlenderDisplayShader::create(b_engine, b_scene)),
tiles_(make_unique<Tiles>())
{
/* Create context while on the main thread. */
gpu_context_create();
}
BlenderDisplayDriver::~BlenderDisplayDriver()
{
gpu_resources_destroy();
}
/* --------------------------------------------------------------------
* Update procedure.
*/
void BlenderDisplayDriver::next_tile_begin()
{
if (!tiles_->current_tile.tile.ready_to_draw()) {
LOG_ERROR
<< "Unexpectedly moving to the next tile without any data provided for current tile.";
return;
}
/* Moving to the next tile without giving render data for the current tile is not an expected
* situation. */
DCHECK(!need_zero_);
/* Texture should have been updated from the PBO at this point. */
DCHECK(!tiles_->current_tile.need_update_texture_pixels);
tiles_->finished_tiles.tiles.emplace_back(std::move(tiles_->current_tile.tile));
}
bool BlenderDisplayDriver::update_begin(const Params &params,
const int texture_width,
const int texture_height)
{
/* Note that it's the responsibility of BlenderDisplayDriver to ensure updating and drawing
* the texture does not happen at the same time. This is achieved indirectly.
*
* When enabling the OpenGL/GPU context, it uses an internal mutex lock DST.gpu_context_lock.
* This same lock is also held when do_draw() is called, which together ensure mutual
* exclusion.
*
* This locking is not performed on the Cycles side, because that would cause lock inversion. */
if (!gpu_context_enable()) {
return false;
}
/* Note: The render window might not draw between tiles. Wait for the previous
* PBO-to-texture copy before reusing the PBO for the next tile. */
blender::GPU_fence_wait(gpu_upload_sync_);
blender::GPU_fence_wait(gpu_render_sync_);
DrawTile &current_tile = tiles_->current_tile.tile;
DisplayGPUPixelBuffer &current_tile_buffer_object = tiles_->current_tile.buffer_object;
/* Clear storage of all finished tiles when display clear is requested.
* Do it when new tile data is provided to handle the display clear flag in a single place.
* It also makes the logic reliable from the whether drawing did happen or not point of view. */
if (need_zero_) {
tiles_->finished_tiles.gl_resources_destroy_and_clear();
need_zero_ = false;
}
/* Update PBO dimensions if needed.
*
* NOTE: Allocate the PBO for the size which will fit the final render resolution (as in,
* at a resolution divider 1. This was we don't need to recreate graphics interoperability
* objects which are costly and which are tied to the specific underlying buffer size.
* The downside of this approach is that when graphics interoperability is not used we are
* sending too much data to blender::GPU when resolution divider is not 1. */
/* TODO(sergey): Investigate whether keeping the PBO exact size of the texture makes non-interop
* mode faster. */
const int buffer_width = params.size.x;
const int buffer_height = params.size.y;
bool interop_recreated = false;
if (!current_tile_buffer_object.gpu_resources_ensure(
buffer_width, buffer_height, interop_recreated) ||
!current_tile.texture.gpu_resources_ensure(texture_width, texture_height))
{
graphics_interop_buffer_.clear();
tiles_->current_tile.gpu_resources_destroy();
gpu_context_disable();
return false;
}
if (interop_recreated) {
graphics_interop_buffer_.clear();
}
/* Store an updated parameters of the current tile.
* In theory it is only needed once per update of the tile, but doing it on every update is
* the easiest and is not expensive. */
tiles_->current_tile.tile.params = params;
return true;
}
static void update_tile_texture_pixels(const DrawTileAndPBO &tile)
{
const DisplayGPUTexture &texture = tile.tile.texture;
if (!DCHECK_NOTNULL(tile.buffer_object.gpu_pixel_buffer)) {
LOG_ERROR << "Display driver tile pixel buffer unavailable.";
return;
}
blender::GPU_texture_update_sub_from_pixel_buffer(texture.gpu_texture,
blender::GPU_DATA_HALF_FLOAT,
tile.buffer_object.gpu_pixel_buffer,
0,
0,
0,
texture.width,
texture.height,
0);
}
void BlenderDisplayDriver::update_end()
{
/* Unpack the PBO into the texture as soon as the new content is provided.
*
* This allows to ensure that the unpacking happens while resources like graphics interop (which
* lifetime is outside of control of the display driver) are still valid, as well as allows to
* move the tile from being current to finished blender::immediately after this call.
*
* One concern with this approach is that if the update happens more often than drawing then
* doing the unpack here occupies blender::GPU transfer for no good reason. However, the render
* scheduler takes care of ensuring updates don't happen that often. In regular applications
* redraw will happen much more often than this update.
*
* On some older blender::GPUs on macOS, there is a driver crash when updating the texture for
* viewport renders while Blender is drawing. As a workaround update texture during draw, under
* assumption that there is no graphics interop on macOS and viewport render has a single tile.
*/
if (!background_ && blender::GPU_type_matches_ex(blender::GPU_DEVICE_NVIDIA,
blender::GPU_OS_MAC,
blender::GPU_DRIVER_ANY,
blender::GPU_BACKEND_ANY))
{
tiles_->current_tile.need_update_texture_pixels = true;
}
else {
update_tile_texture_pixels(tiles_->current_tile);
}
/* Ensure blender::GPU fence exists to synchronize upload. */
blender::GPU_fence_signal(gpu_upload_sync_);
blender::GPU_flush();
gpu_context_disable();
has_update_cond_.notify_all();
}
/* --------------------------------------------------------------------
* Texture buffer mapping.
*/
half4 *BlenderDisplayDriver::map_texture_buffer()
{
/* With multi device rendering, Cycles can switch between using graphics interop
* and not. For the denoised image it may be able to use graphics interop as that
* buffer is written to by one device, while the noisy renders can not use it.
*
* We need to clear the graphics interop buffer on that switch, as blender::GPU_pixel_buffer_map
* may recreate the buffer or handle. */
graphics_interop_buffer_.clear();
blender::GPUPixelBuffer *pix_buf = tiles_->current_tile.buffer_object.gpu_pixel_buffer;
if (!DCHECK_NOTNULL(pix_buf)) {
LOG_ERROR << "Display driver tile pixel buffer unavailable.";
return nullptr;
}
half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(blender::GPU_pixel_buffer_map(pix_buf));
if (!mapped_rgba_pixels) {
LOG_ERROR << "Error mapping BlenderDisplayDriver pixel buffer object.";
}
return mapped_rgba_pixels;
}
void BlenderDisplayDriver::unmap_texture_buffer()
{
blender::GPUPixelBuffer *pix_buf = tiles_->current_tile.buffer_object.gpu_pixel_buffer;
if (!DCHECK_NOTNULL(pix_buf)) {
LOG_ERROR << "Display driver tile pixel buffer unavailable.";
return;
}
blender::GPU_pixel_buffer_unmap(pix_buf);
}
/* --------------------------------------------------------------------
* Graphics interoperability.
*/
GraphicsInteropDevice BlenderDisplayDriver::graphics_interop_get_device()
{
GraphicsInteropDevice interop_device;
switch (blender::GPU_backend_get_type()) {
case blender::GPU_BACKEND_OPENGL:
interop_device.type = GraphicsInteropDevice::OPENGL;
break;
case blender::GPU_BACKEND_VULKAN:
interop_device.type = GraphicsInteropDevice::VULKAN;
break;
case blender::GPU_BACKEND_METAL:
interop_device.type = GraphicsInteropDevice::METAL;
break;
case blender::GPU_BACKEND_NONE:
case blender::GPU_BACKEND_ANY:
interop_device.type = GraphicsInteropDevice::NONE;
break;
}
blender::Span<uint8_t> uuid = blender::GPU_platform_uuid();
interop_device.uuid.resize(uuid.size());
std::copy_n(uuid.data(), uuid.size(), interop_device.uuid.data());
return interop_device;
}
void BlenderDisplayDriver::graphics_interop_update_buffer()
{
if (graphics_interop_buffer_.is_empty()) {
GraphicsInteropDevice::Type type = GraphicsInteropDevice::NONE;
switch (blender::GPU_backend_get_type()) {
case blender::GPU_BACKEND_OPENGL:
type = GraphicsInteropDevice::OPENGL;
break;
case blender::GPU_BACKEND_VULKAN:
type = GraphicsInteropDevice::VULKAN;
break;
case blender::GPU_BACKEND_METAL:
type = GraphicsInteropDevice::METAL;
break;
case blender::GPU_BACKEND_NONE:
case blender::GPU_BACKEND_ANY:
break;
}
blender::GPUPixelBufferNativeHandle handle = blender::GPU_pixel_buffer_get_native_handle(
tiles_->current_tile.buffer_object.gpu_pixel_buffer);
graphics_interop_buffer_.assign(type, handle.handle, handle.size);
}
}
void BlenderDisplayDriver::graphics_interop_activate()
{
gpu_context_enable();
}
void BlenderDisplayDriver::graphics_interop_deactivate()
{
gpu_context_disable();
}
/* --------------------------------------------------------------------
* Drawing.
*/
void BlenderDisplayDriver::zero()
{
need_zero_ = true;
}
void BlenderDisplayDriver::set_zoom(const float zoom_x, const float zoom_y)
{
zoom_ = make_float2(zoom_x, zoom_y);
}
/* Update vertex buffer with new coordinates of vertex positions and texture coordinates.
* This buffer is used to render texture in the viewport.
*
* NOTE: The buffer needs to be bound. */
static void vertex_draw(const DisplayDriver::Params &params,
const int texcoord_attribute,
const int position_attribute)
{
const int x = params.full_offset.x;
const int y = params.full_offset.y;
const int width = params.size.x;
const int height = params.size.y;
blender::immBegin(blender::GPU_PRIM_TRI_STRIP, 4);
blender::immAttr2f(texcoord_attribute, 1.0f, 0.0f);
blender::immVertex2f(position_attribute, x + width, y);
blender::immAttr2f(texcoord_attribute, 1.0f, 1.0f);
blender::immVertex2f(position_attribute, x + width, y + height);
blender::immAttr2f(texcoord_attribute, 0.0f, 0.0f);
blender::immVertex2f(position_attribute, x, y);
blender::immAttr2f(texcoord_attribute, 0.0f, 1.0f);
blender::immVertex2f(position_attribute, x, y + height);
blender::immEnd();
}
static void draw_tile(const float2 &zoom,
const int texcoord_attribute,
const int position_attribute,
const DrawTile &draw_tile)
{
if (!draw_tile.ready_to_draw()) {
return;
}
const DisplayGPUTexture &texture = draw_tile.texture;
if (!DCHECK_NOTNULL(texture.gpu_texture)) {
LOG_ERROR << "Display driver tile blender::GPU texture resource unavailable.";
return;
}
/* Trick to keep sharp rendering without jagged edges on all blender::GPUs.
*
* The idea here is to enforce driver to use linear interpolation when the image is zoomed out.
* For the render result with a resolution divider in effect we always use nearest interpolation.
*
* Use explicit MIN assignment to make sure the driver does not have an undefined behavior at
* the zoom level 1. The MAG filter is always NEAREST. */
const float zoomed_width = draw_tile.params.size.x * zoom.x;
const float zoomed_height = draw_tile.params.size.y * zoom.y;
if (texture.width != draw_tile.params.size.x || texture.height != draw_tile.params.size.y) {
/* Resolution divider is different from 1, force nearest interpolation. */
blender::GPU_texture_bind_ex(
texture.gpu_texture, blender::GPUSamplerState::default_sampler(), 0);
}
else if (zoomed_width - draw_tile.params.size.x > -0.5f ||
zoomed_height - draw_tile.params.size.y > -0.5f)
{
blender::GPU_texture_bind_ex(
texture.gpu_texture, blender::GPUSamplerState::default_sampler(), 0);
}
else {
blender::GPU_texture_bind_ex(texture.gpu_texture, {blender::GPU_SAMPLER_FILTERING_LINEAR}, 0);
}
/* Draw at the parameters for which the texture has been updated for. This allows to always draw
* texture during bordered-rendered camera view without flickering. The validness of the display
* parameters for a texture is guaranteed by the initial "clear" state which makes drawing to
* have an early output.
*
* Such approach can cause some extra "jelly" effect during panning, but it is not more jelly
* than overlay of selected objects. Also, it's possible to redraw texture at an intersection of
* the texture draw parameters and the latest updated draw parameters (although, complexity of
* doing it might not worth it. */
vertex_draw(draw_tile.params, texcoord_attribute, position_attribute);
}
void BlenderDisplayDriver::flush()
{
/* This is called from the render thread that also calls update_begin/end, right before ending
* the render loop. We wait for any queued PBO and render commands to be done, before destroying
* the render thread and activating the context in the main thread to destroy resources.
*
* If we don't do this, the NVIDIA driver hangs for a few seconds for when ending 3D viewport
* rendering, for unknown reasons. This was found with NVIDIA driver version 470.73 and a Quadro
* RTX 6000 on Linux. */
if (!gpu_context_enable()) {
return;
}
blender::GPU_fence_wait(gpu_upload_sync_);
blender::GPU_fence_wait(gpu_render_sync_);
gpu_context_disable();
}
void BlenderDisplayDriver::draw(const Params &params)
{
if (b_rv3d_ && (b_rv3d_->rflag & (blender::RV3D_NAVIGATING | blender::RV3D_PAINTING))) {
/* Before drawing, wait that an update to the texture has actually occurred, to synchronize
* rendering of Cycles with Blender. Use a timeout to prevent user interface in the main thread
* from becoming unresponsive when rendering is too heavy. */
thread_scoped_lock lock(has_update_mutex_);
has_update_cond_.wait_for(lock, std::chrono::milliseconds(33));
lock.unlock();
}
gpu_context_lock();
if (need_zero_) {
/* Texture is requested to be cleared and was not yet cleared.
*
* Do early return which should be equivalent of drawing all-zero texture.
* Watch out for the lock though so that the clear happening during update is properly
* synchronized here. */
gpu_context_unlock();
return;
}
blender::GPU_fence_wait(gpu_upload_sync_);
blender::GPU_blend(blender::GPU_BLEND_ALPHA_PREMULT);
blender::gpu::Shader *active_shader = display_shader_->bind(params.full_size.x,
params.full_size.y);
blender::GPUVertFormat *format = blender::immVertexFormat();
const int texcoord_attribute = blender::GPU_vertformat_attr_add(
format,
ccl::BlenderDisplayShader::tex_coord_attribute_name,
blender::gpu::VertAttrType::SFLOAT_32_32);
const int position_attribute = blender::GPU_vertformat_attr_add(
format,
ccl::BlenderDisplayShader::position_attribute_name,
blender::gpu::VertAttrType::SFLOAT_32_32);
/* NOTE: Shader is bound again through IMM to register this shader with the IMM module
* and perform required setup for IMM rendering. This is required as the IMM module
* needs to be aware of which shader is bound, and the main display shader
* is bound externally. */
blender::immBindShader(active_shader);
if (tiles_->current_tile.need_update_texture_pixels) {
update_tile_texture_pixels(tiles_->current_tile);
tiles_->current_tile.need_update_texture_pixels = false;
}
draw_tile(zoom_, texcoord_attribute, position_attribute, tiles_->current_tile.tile);
for (const DrawTile &tile : tiles_->finished_tiles.tiles) {
draw_tile(zoom_, texcoord_attribute, position_attribute, tile);
}
/* Reset IMM shader bind state. */
blender::immUnbindProgram();
display_shader_->unbind();
blender::GPU_blend(blender::GPU_BLEND_NONE);
blender::GPU_fence_signal(gpu_render_sync_);
blender::GPU_flush();
gpu_context_unlock();
LOG_TRACE << "Display driver number of textures: " << DisplayGPUTexture::num_used;
LOG_TRACE << "Display driver number of PBOs: " << DisplayGPUPixelBuffer::num_used;
}
void BlenderDisplayDriver::gpu_context_create()
{
if (!RE_engine_gpu_context_create(&b_engine_)) {
LOG_ERROR << "Error creating blender::GPU context.";
return;
}
/* Create global blender::GPU resources for display driver. */
if (!gpu_resources_create()) {
LOG_ERROR << "Error creating blender::GPU resources for Display Driver.";
return;
}
}
bool BlenderDisplayDriver::gpu_context_enable()
{
return RE_engine_gpu_context_enable(&b_engine_);
}
void BlenderDisplayDriver::gpu_context_disable()
{
RE_engine_gpu_context_disable(&b_engine_);
}
void BlenderDisplayDriver::gpu_context_destroy()
{
RE_engine_gpu_context_destroy(&b_engine_);
}
void BlenderDisplayDriver::gpu_context_lock()
{
RE_engine_gpu_context_lock(&b_engine_);
}
void BlenderDisplayDriver::gpu_context_unlock()
{
RE_engine_gpu_context_unlock(&b_engine_);
}
bool BlenderDisplayDriver::gpu_resources_create()
{
/* Ensure context is active for resource creation. */
if (!gpu_context_enable()) {
LOG_ERROR << "Error enabling blender::GPU context.";
return false;
}
gpu_upload_sync_ = blender::GPU_fence_create();
gpu_render_sync_ = blender::GPU_fence_create();
if (!DCHECK_NOTNULL(gpu_upload_sync_) || !DCHECK_NOTNULL(gpu_render_sync_)) {
LOG_ERROR << "Error creating blender::GPU synchronization primitives.";
assert(0);
return false;
}
gpu_context_disable();
return true;
}
void BlenderDisplayDriver::gpu_resources_destroy()
{
gpu_context_enable();
display_shader_.reset();
graphics_interop_buffer_.clear();
tiles_->current_tile.gpu_resources_destroy();
tiles_->finished_tiles.gl_resources_destroy_and_clear();
/* Fences. */
if (gpu_render_sync_) {
blender::GPU_fence_free(gpu_render_sync_);
gpu_render_sync_ = nullptr;
}
if (gpu_upload_sync_) {
blender::GPU_fence_free(gpu_upload_sync_);
gpu_upload_sync_ = nullptr;
}
gpu_context_disable();
gpu_context_destroy();
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,144 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <atomic>
#include "session/display_driver.h"
#include "util/thread.h"
#include "util/unique_ptr.h"
namespace blender {
struct GPUContext;
struct GPUFence;
struct RenderEngine;
struct Scene;
namespace gpu {
class Shader;
} // namespace gpu
} // namespace blender
CCL_NAMESPACE_BEGIN
/* Base class of shader used for display driver rendering. */
class BlenderDisplayShader {
public:
static constexpr const char *position_attribute_name = "pos";
static constexpr const char *tex_coord_attribute_name = "texCoord";
/* Create shader implementation suitable for the given render engine and scene configuration. */
static unique_ptr<BlenderDisplayShader> create(blender::RenderEngine &b_engine,
blender::Scene &b_scene);
BlenderDisplayShader() = default;
virtual ~BlenderDisplayShader() = default;
virtual blender::gpu::Shader *bind(const int width, const int height) = 0;
virtual void unbind() = 0;
/* Get attribute location for position and texture coordinate respectively.
* NOTE: The shader needs to be bound to have access to those. */
virtual int get_position_attrib_location();
virtual int get_tex_coord_attrib_location();
protected:
/* Get program of this display shader.
* NOTE: The shader needs to be bound to have access to this. */
virtual blender::gpu::Shader *get_shader_program() = 0;
/* Cached values of various OpenGL resources. */
int position_attribute_location_ = -1;
int tex_coord_attribute_location_ = -1;
};
class BlenderDisplaySpaceShader : public BlenderDisplayShader {
public:
BlenderDisplaySpaceShader(blender::RenderEngine &b_engine, blender::Scene &b_scene);
blender::gpu::Shader *bind(const int width, const int height) override;
void unbind() override;
protected:
blender::gpu::Shader *get_shader_program() override;
blender::RenderEngine &b_engine_;
blender::Scene &b_scene_;
/* Cached values of various OpenGL resources. */
blender::gpu::Shader *shader_program_ = nullptr;
};
/* Display driver implementation which is specific for Blender viewport integration. */
class BlenderDisplayDriver : public DisplayDriver {
public:
BlenderDisplayDriver(blender::RenderEngine &b_engine,
blender::Scene &b_scene,
blender::RegionView3D *b_rv3d,
const bool background);
~BlenderDisplayDriver() override;
void graphics_interop_activate() override;
void graphics_interop_deactivate() override;
void zero() override;
void set_zoom(const float zoom_x, const float zoom_y);
protected:
void next_tile_begin() override;
bool update_begin(const Params &params,
const int texture_width,
const int texture_height) override;
void update_end() override;
half4 *map_texture_buffer() override;
void unmap_texture_buffer() override;
GraphicsInteropDevice graphics_interop_get_device() override;
void graphics_interop_update_buffer() override;
void draw(const Params &params) override;
void flush() override;
/* Helper function which allocates new GPU context. */
void gpu_context_create();
bool gpu_context_enable();
void gpu_context_disable();
void gpu_context_destroy();
void gpu_context_lock();
void gpu_context_unlock();
/* Create GPU resources used by the display driver. */
bool gpu_resources_create();
/* Destroy all GPU resources which are being used by this object. */
void gpu_resources_destroy();
blender::RenderEngine &b_engine_;
blender::RegionView3D *b_rv3d_;
bool background_;
/* Content of the display is to be filled with zeroes. */
std::atomic<bool> need_zero_ = true;
unique_ptr<BlenderDisplayShader> display_shader_;
/* Opaque storage for an internal state and data for tiles. */
struct Tiles;
unique_ptr<Tiles> tiles_;
blender::GPUFence *gpu_render_sync_ = nullptr;
blender::GPUFence *gpu_upload_sync_ = nullptr;
float2 zoom_ = make_float2(1.0f, 1.0f);
thread_condition_variable has_update_cond_;
thread_mutex has_update_mutex_;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,312 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/curves.h"
#include "scene/hair.h"
#include "scene/light.h"
#include "scene/mesh.h"
#include "scene/object.h"
#include "scene/pointcloud.h"
#include "scene/volume.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "util/task.h"
#include "BKE_material.hh"
#include "DNA_light_types.h"
#include "DNA_material_types.h"
CCL_NAMESPACE_BEGIN
static Geometry::Type determine_geom_type(BObjectInfo &b_ob_info, bool use_particle_hair)
{
if (GS(b_ob_info.object_data->name) == blender::ID_LA) {
blender::Light &b_light = *blender::id_cast<blender::Light *>(b_ob_info.object_data);
switch (b_light.type) {
case blender::LA_LOCAL:
return Geometry::POINT_LIGHT;
case blender::LA_SPOT:
return Geometry::SPOT_LIGHT;
case blender::LA_SUN:
return Geometry::SUN_LIGHT;
case blender::LA_AREA:
return Geometry::AREA_LIGHT;
default:
/* Should be handled in `sync_background_light()`. */
assert(false);
return Geometry::BACKGROUND_LIGHT;
}
}
if (GS(b_ob_info.object_data->name) == blender::ID_CV || use_particle_hair) {
return Geometry::HAIR;
}
if (GS(b_ob_info.object_data->name) == blender::ID_PT) {
return Geometry::POINTCLOUD;
}
if (GS(b_ob_info.object_data->name) == blender::ID_VO ||
(b_ob_info.object_data ==
object_get_data(*b_ob_info.real_object, b_ob_info.use_adaptive_subdivision) &&
object_fluid_gas_domain_find(*b_ob_info.real_object)))
{
return Geometry::VOLUME;
}
return Geometry::MESH;
}
array<Node *> BlenderSync::find_used_shaders(blender::Object &b_ob)
{
array<Node *> used_shaders;
if (b_ob.type == blender::OB_LAMP) {
find_shader(static_cast<blender::ID *>(b_ob.data), used_shaders, scene->default_light);
return used_shaders;
}
blender::Material *material_override = view_layer.material_override;
Shader *default_shader = (b_ob.type == blender::OB_VOLUME) ? scene->default_volume :
scene->default_surface;
for (const int i : blender::IndexRange(BKE_object_material_count_eval(&b_ob))) {
if (material_override) {
find_shader(&material_override->id, used_shaders, default_shader);
}
else {
blender::Material *b_material = BKE_object_material_get(&b_ob, i + 1);
find_shader(reinterpret_cast<blender::ID *>(b_material), used_shaders, default_shader);
}
}
if (used_shaders.size() == 0) {
if (material_override) {
find_shader(&material_override->id, used_shaders, default_shader);
}
else {
used_shaders.push_back_slow(default_shader);
}
}
return used_shaders;
}
Geometry *BlenderSync::sync_geometry(BObjectInfo &b_ob_info,
bool object_updated,
bool use_particle_hair,
TaskPool *task_pool)
{
/* Test if we can instance or if the object is modified. */
const Geometry::Type geom_type = determine_geom_type(b_ob_info, use_particle_hair);
blender::ID *const b_key_id = (b_ob_info.is_real_object_data() &&
BKE_object_is_modified(*b_ob_info.real_object)) ?
&b_ob_info.real_object->id :
b_ob_info.object_data;
const GeometryKey key(b_key_id, geom_type);
/* Find shader indices. */
array<Node *> used_shaders = find_used_shaders(*b_ob_info.iter_object);
/* Ensure we only sync instanced geometry once. */
Geometry *geom = geometry_map.find(key);
if (geom) {
if (geometry_synced.contains(geom)) {
return geom;
}
}
/* Test if we need to sync. */
bool sync = true;
if (geom == nullptr) {
/* Add new geometry if it did not exist yet. */
if (geom_type == Geometry::POINT_LIGHT) {
geom = scene->create_light_node<PointLight>();
}
else if (geom_type == Geometry::SPOT_LIGHT) {
geom = scene->create_light_node<SpotLight>();
}
else if (geom_type == Geometry::SUN_LIGHT) {
geom = scene->create_light_node<SunLight>();
}
else if (geom_type == Geometry::AREA_LIGHT) {
geom = scene->create_light_node<AreaLight>();
}
else if (geom_type == Geometry::HAIR) {
geom = scene->create_node<Hair>();
}
else if (geom_type == Geometry::VOLUME) {
geom = scene->create_node<Volume>();
}
else if (geom_type == Geometry::POINTCLOUD) {
geom = scene->create_node<PointCloud>();
}
else {
assert(geom_type == Geometry::MESH);
geom = scene->create_node<Mesh>();
}
geometry_map.add(key, geom);
}
else {
/* Test if we need to update existing geometry. */
sync = geometry_map.update(geom, b_key_id);
}
if (!sync) {
/* If transform was applied to geometry, need full update. */
if (object_updated && geom->transform_applied) {
;
}
/* Test if shaders changed, these can be object level so geometry
* does not get tagged for recalc. */
else if (geom->get_used_shaders() != used_shaders) {
;
}
else {
/* Even if not tagged for recalc, we may need to sync anyway
* because the shader needs different geometry attributes. */
bool attribute_recalc = false;
for (Node *node : geom->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->need_update_geometry()) {
attribute_recalc = true;
}
}
if (!attribute_recalc) {
return geom;
}
}
}
geometry_synced.insert(geom);
geom->name = ustring(BKE_id_name(*b_ob_info.object_data));
/* Store the shaders immediately for the object attribute code. */
geom->set_used_shaders(used_shaders);
auto sync_func = [this, geom_type, b_ob_info, geom]() mutable {
if (progress.get_cancel()) {
return;
}
progress.set_sync_status("Synchronizing object", BKE_id_name(b_ob_info.real_object->id));
if (geom->is_light()) {
Light *light = static_cast<Light *>(geom);
sync_light(b_ob_info, light);
}
else if (geom_type == Geometry::HAIR) {
Hair *hair = static_cast<Hair *>(geom);
sync_hair(b_ob_info, hair);
}
else if (geom_type == Geometry::VOLUME) {
Volume *volume = static_cast<Volume *>(geom);
sync_volume(b_ob_info, volume);
}
else if (geom_type == Geometry::POINTCLOUD) {
PointCloud *pointcloud = static_cast<PointCloud *>(geom);
sync_pointcloud(pointcloud, b_ob_info);
}
else {
Mesh *mesh = static_cast<Mesh *>(geom);
sync_mesh(b_ob_info, mesh);
}
};
/* Defer the actual geometry sync to the task_pool for multithreading */
if (task_pool) {
task_pool->push(sync_func);
}
else {
sync_func();
}
return geom;
}
void BlenderSync::sync_geometry_motion(BObjectInfo &b_ob_info,
Object *object,
const float motion_time,
bool use_particle_hair,
TaskPool *task_pool)
{
/* Ensure we only sync instanced geometry once. */
Geometry *geom = object->get_geometry();
if (geometry_motion_synced.contains(geom) || geometry_motion_attribute_synced.contains(geom)) {
return;
}
geometry_motion_synced.insert(geom);
/* Ensure we only motion sync geometry that also had geometry synced, to avoid
* unnecessary work and to ensure that its attributes were clear. */
if (!geometry_synced.contains(geom)) {
return;
}
/* Nothing to do for lights. */
if (geom->is_light()) {
return;
}
/* If the geometry already has motion blur from a velocity attribute, don't
* set the geometry motion steps again.
*
* Otherwise, setting geometry motion steps is done here to avoid concurrency issues.
* - It can't be done earlier in sync_object_motion_init because sync_geometry
* runs in parallel, and has_motion_blur would check attributes while
* sync_geometry is potentially creating the attribute from velocity.
* - It needs to happen before the parallel motion sync that happens right after
* this, because that can create the attribute from neighboring frames.
* Copying the motion steps from the object here solves this. */
if (!geom->has_motion_blur()) {
geom->set_motion_steps(object->get_motion().size());
}
/* Find time matching motion step required by geometry. */
const int motion_step = geom->motion_step(motion_time);
if (motion_step < 0) {
return;
}
auto sync_func = [this, b_ob_info, use_particle_hair, motion_step, geom]() mutable {
if (progress.get_cancel()) {
return;
}
if (GS(b_ob_info.object_data->name) == blender::ID_CV || use_particle_hair) {
Hair *hair = static_cast<Hair *>(geom);
sync_hair_motion(b_ob_info, hair, motion_step);
}
else if (GS(b_ob_info.object_data->name) == blender::ID_VO ||
object_fluid_gas_domain_find(*b_ob_info.real_object))
{
/* No volume motion blur support yet. */
}
else if (GS(b_ob_info.object_data->name) == blender::ID_PT) {
PointCloud *pointcloud = static_cast<PointCloud *>(geom);
sync_pointcloud_motion(pointcloud, b_ob_info, motion_step);
}
else {
Mesh *mesh = static_cast<Mesh *>(geom);
sync_mesh_motion(b_ob_info, mesh, motion_step);
}
};
/* Defer the actual geometry sync to the task_pool for multithreading */
if (task_pool) {
task_pool->push(sync_func);
}
else {
sync_func();
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,313 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <cstring>
#include "scene/geometry.h"
#include "scene/scene.h"
#include "util/map.h"
#include "util/set.h"
namespace blender {
struct ID;
}
CCL_NAMESPACE_BEGIN
/* ID Map
*
* Utility class to map between Blender datablocks and Cycles data structures,
* and keep track of recalc tags from the dependency graph. */
template<typename K, typename T, typename Flags = uint> class id_map {
public:
id_map(Scene *scene_) : scene(scene_) {}
~id_map()
{
set<T *> nodes;
typename map<K, T *>::iterator jt;
for (jt = b_map.begin(); jt != b_map.end(); jt++) {
nodes.insert(jt->second);
}
scene->delete_nodes(nodes);
}
T *find(const K &key)
{
if (b_map.find(key) != b_map.end()) {
T *data = b_map[key];
return data;
}
return nullptr;
}
void set_recalc(void *id_ptr)
{
b_recalc.insert(id_ptr);
}
bool check_recalc(const blender::ID *id)
{
return id && b_recalc.contains(id);
}
bool has_recalc()
{
return !(b_recalc.empty());
}
void pre_sync()
{
used_set.clear();
}
/* Add new data. */
void add(const K &key, T *data)
{
assert(find(key) == nullptr);
b_map[key] = data;
used(data);
}
/* Update existing data. */
bool update(T *data, const blender::ID *id)
{
return update(data, id, id);
}
bool update(T *data, const blender::ID *id, const blender::ID *parent)
{
bool recalc = (b_recalc.contains(id));
if (parent && parent != id) {
recalc = recalc || (b_recalc.contains(parent));
}
used(data);
return recalc;
}
/* Combined add and update as needed. */
bool add_or_update(T **r_data, const blender::ID *id)
{
return add_or_update(r_data, id, id, id);
}
bool add_or_update(T **r_data, const blender::ID *id, const K &key)
{
return add_or_update(r_data, id, id, key);
}
bool add_or_update(T **r_data, const blender::ID *id, const blender::ID *parent, const K &key)
{
T *data = find(key);
bool recalc;
if (!data) {
/* Add data if it didn't exist yet. */
data = scene->create_node<T>();
add(key, data);
recalc = true;
}
else {
/* check if updated needed. */
recalc = update(data, id, parent);
}
*r_data = data;
return recalc;
}
/* Combined add or update for convenience. */
bool is_used(const K &key)
{
T *data = find(key);
return (data) ? used_set.find(data) != used_set.end() : false;
}
void used(T *data)
{
/* tag data as still in use */
used_set.insert(data);
}
void set_default(T *data)
{
b_map[nullptr] = data;
}
void post_sync(bool do_delete = true)
{
map<K, T *> new_map;
set<T *> nodes_to_delete;
using TMapPair = pair<const K, T *>;
typename map<K, T *>::iterator jt;
for (jt = b_map.begin(); jt != b_map.end(); jt++) {
TMapPair &pair = *jt;
if (do_delete && used_set.find(pair.second) == used_set.end()) {
flags.erase(pair.second);
nodes_to_delete.insert(pair.second);
}
else {
new_map[pair.first] = pair.second;
}
}
if (!nodes_to_delete.empty()) {
scene->delete_nodes(nodes_to_delete);
}
used_set.clear();
b_recalc.clear();
b_map = new_map;
}
const map<K, T *> &key_to_scene_data()
{
return b_map;
}
bool test_flag(T *data, Flags val)
{
typename map<T *, uint>::iterator it = flags.find(data);
return it != flags.end() && (it->second & (1 << val)) != 0;
}
void set_flag(T *data, Flags val)
{
flags[data] |= (1 << val);
}
void clear_flag(T *data, Flags val)
{
typename map<T *, uint>::iterator it = flags.find(data);
if (it != flags.end()) {
it->second &= ~(1 << val);
if (it->second == 0) {
flags.erase(it);
}
}
}
protected:
map<K, T *> b_map;
set<T *> used_set;
map<T *, uint> flags;
set<const void *> b_recalc;
Scene *scene;
};
/* Object Key
*
* To uniquely identify instances, we use the parent, object and persistent instance ID.
* We also export separate object for a mesh and its particle hair. */
enum { OBJECT_PERSISTENT_ID_SIZE = 8 /* MAX_DUPLI_RECUR in Blender. */ };
struct ObjectKey {
void *parent;
int id[OBJECT_PERSISTENT_ID_SIZE];
void *ob;
bool use_particle_hair;
ObjectKey(void *parent_,
const int id_[OBJECT_PERSISTENT_ID_SIZE],
void *ob_,
bool use_particle_hair_)
: parent(parent_), ob(ob_), use_particle_hair(use_particle_hair_)
{
if (id_) {
memcpy(id, id_, sizeof(id));
}
else {
memset(id, 0, sizeof(id));
}
}
bool operator<(const ObjectKey &k) const
{
if (ob < k.ob) {
return true;
}
if (ob == k.ob) {
if (parent < k.parent) {
return true;
}
if (parent == k.parent) {
if (use_particle_hair < k.use_particle_hair) {
return true;
}
if (use_particle_hair == k.use_particle_hair) {
return memcmp(id, k.id, sizeof(id)) < 0;
}
}
}
return false;
}
};
/* Geometry Key
*
* We export separate geometry for a mesh and its particle hair, so key needs to
* distinguish between them. */
struct GeometryKey {
void *id;
Geometry::Type geometry_type;
GeometryKey(void *id, Geometry::Type geometry_type) : id(id), geometry_type(geometry_type) {}
bool operator<(const GeometryKey &k) const
{
if (id < k.id) {
return true;
}
if (id == k.id) {
if (geometry_type < k.geometry_type) {
return true;
}
}
return false;
}
};
/* Particle System Key */
struct ParticleSystemKey {
void *ob;
int id[OBJECT_PERSISTENT_ID_SIZE];
ParticleSystemKey(void *ob_, const int id_[OBJECT_PERSISTENT_ID_SIZE]) : ob(ob_)
{
if (id_) {
memcpy(id, id_, sizeof(id));
}
else {
memset(id, 0, sizeof(id));
}
}
bool operator<(const ParticleSystemKey &k) const
{
/* first id is particle index, we don't compare that */
if (ob < k.ob) {
return true;
}
if (ob == k.ob) {
return memcmp(id + 1, k.id + 1, sizeof(int) * (OBJECT_PERSISTENT_ID_SIZE - 1)) < 0;
}
return false;
}
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,262 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include <algorithm>
#include "DNA_image_types.h"
#include "IMB_imbuf_types.hh"
#include "BKE_image.hh"
#include "blender/image.h"
#include "blender/session.h"
#include "util/half.h"
#include "util/types_float4.h"
CCL_NAMESPACE_BEGIN
/* Packed Images */
BlenderImageLoader::BlenderImageLoader(blender::Image *b_image,
blender::ImageUser *b_iuser,
const int frame,
const int tile_number,
const bool is_preview_render)
: b_image(b_image),
b_iuser(*b_iuser),
/* Don't free cache for preview render to avoid race condition from #93560, to be fixed
* properly later as we are close to release. */
free_cache(!is_preview_render && !BKE_image_has_loaded_ibuf(b_image)),
cached_update_count(b_image->runtime->update_count)
{
this->b_iuser.framenr = frame;
if (b_image->source != blender::IMA_SRC_TILED) {
/* Image sequences currently not supported by this image loader. */
assert(b_image->source != blender::IMA_SRC_SEQUENCE);
}
else {
/* Set UDIM tile, each can have different resolution. */
this->b_iuser.tile = tile_number;
}
}
bool BlenderImageLoader::load_metadata(ImageMetaData &metadata,
const ImageLoaderParams & /*params*/,
Progress & /*progress*/)
{
bool is_float = false;
bool is_data = false;
{
void *lock;
blender::ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
if (ibuf) {
is_float = ibuf->float_data() != nullptr;
is_data = ibuf->colorspace_is_data();
metadata.width = ibuf->x;
metadata.height = ibuf->y;
metadata.channels = (is_float) ? ibuf->channels : 4;
metadata.is_unassociated_alpha = !is_float;
}
else {
metadata.width = 0;
metadata.height = 0;
metadata.channels = 0;
}
BKE_image_release_ibuf(b_image, ibuf, lock);
}
if (is_float) {
if (metadata.channels == 1) {
metadata.type = IMAGE_DATA_TYPE_FLOAT;
}
else {
metadata.channels = 4;
metadata.type = IMAGE_DATA_TYPE_FLOAT4;
}
/* Float images are already converted on the Blender side,
* no need to do anything in Cycles. */
metadata.colorspace = (is_data) ? u_colorspace_data : u_colorspace_scene_linear;
}
else {
/* In some cases (e.g. #94135), the colorspace setting in Blender gets updated as part of the
* metadata queries in this function, so update the colorspace setting here. */
metadata.colorspace = (is_data) ? u_colorspace_data :
ustring(b_image->colorspace_settings.name);
metadata.type = IMAGE_DATA_TYPE_BYTE4;
}
return true;
}
static void load_float_pixels(const blender::ImBuf *ibuf,
const ImageMetaData &metadata,
float *out_pixels)
{
const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
const int out_channels = metadata.channels;
const int in_channels = ibuf->channels;
const float *in_pixels = ibuf->float_data();
if (in_pixels && out_channels == in_channels) {
/* Straight copy pixel data. */
memcpy(out_pixels, in_pixels, num_pixels * out_channels * sizeof(float));
}
else if (in_pixels && out_channels == 4) {
/* Fill channels to 4. */
float *out_pixel = out_pixels;
const float *in_pixel = in_pixels;
for (size_t i = 0; i < num_pixels; i++) {
out_pixel[0] = in_pixel[0];
out_pixel[1] = (in_channels >= 2) ? in_pixel[1] : 0.0f;
out_pixel[2] = (in_channels >= 3) ? in_pixel[2] : 0.0f;
out_pixel[3] = (in_channels >= 4) ? in_pixel[3] : 1.0f;
out_pixel += out_channels;
in_pixel += in_channels;
}
}
else {
/* Missing or invalid pixel data. */
if (out_channels == 1) {
std::fill(out_pixels, out_pixels + num_pixels, 0.0f);
}
else {
std::fill((float4 *)out_pixels,
(float4 *)out_pixels + num_pixels,
make_float4(1.0f, 0.0f, 1.0f, 1.0f));
}
}
}
static void load_half_pixels(const blender::ImBuf *ibuf,
const ImageMetaData &metadata,
half *out_pixels)
{
/* Half float. Blender does not have a half type, but in some cases
* we up-sample byte to half to avoid precision loss for colorspace
* conversion. */
const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
const int out_channels = metadata.channels;
const uchar *in_pixels = ibuf->byte_data();
if (in_pixels) {
/* Convert uchar to half. */
const uchar *in_pixel = in_pixels;
half *out_pixel = out_pixels;
for (size_t i = 0; i < num_pixels; i++) {
for (int c = 0; c < out_channels; c++, in_pixel++, out_pixel++) {
*out_pixel = float_to_half_image(util_image_cast_to_float(*in_pixel));
}
}
}
else {
/* Missing or invalid pixel data. */
if (out_channels == 1) {
std::fill(out_pixels, out_pixels + num_pixels, float_to_half_image(0.0f));
}
else {
std::fill((half4 *)out_pixels,
(half4 *)out_pixels + num_pixels,
float4_to_half4_display(make_float4(1.0f, 0.0f, 1.0f, 1.0f)));
}
}
}
static void load_byte_pixels(const blender::ImBuf *ibuf,
const ImageMetaData &metadata,
uchar *out_pixels)
{
const size_t num_pixels = ((size_t)metadata.width) * metadata.height;
const int out_channels = metadata.channels;
const int in_channels = 4;
const uchar *in_pixels = ibuf->byte_data();
if (in_pixels) {
/* Straight copy pixel data. */
memcpy(out_pixels, in_pixels, num_pixels * in_channels * sizeof(unsigned char));
}
else {
/* Missing or invalid pixel data. */
if (out_channels == 1) {
std::fill(out_pixels, out_pixels + num_pixels, 0.0f);
}
else {
std::fill(
(uchar4 *)out_pixels, (uchar4 *)out_pixels + num_pixels, make_uchar4(255, 0, 255, 255));
}
}
}
bool BlenderImageLoader::load_pixels(const ImageMetaData &metadata, void *out_pixels)
{
void *lock;
blender::ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
/* Image changed since we requested metadata, assume we'll get a signal to reload it later. */
const bool mismatch = (ibuf == nullptr || ibuf->x != metadata.width ||
ibuf->y != metadata.height);
if (!mismatch) {
if (metadata.type == IMAGE_DATA_TYPE_FLOAT || metadata.type == IMAGE_DATA_TYPE_FLOAT4) {
load_float_pixels(ibuf, metadata, (float *)out_pixels);
}
else if (metadata.type == IMAGE_DATA_TYPE_HALF || metadata.type == IMAGE_DATA_TYPE_HALF4) {
load_half_pixels(ibuf, metadata, (half *)out_pixels);
}
else {
load_byte_pixels(ibuf, metadata, (uchar *)out_pixels);
}
}
BKE_image_release_ibuf(b_image, ibuf, lock);
/* Free image buffers to save memory during render. */
if (free_cache) {
BKE_image_free_buffers_ex(b_image, true);
}
if (!mismatch) {
metadata.conform_pixels(out_pixels);
}
return !mismatch;
}
string BlenderImageLoader::name() const
{
return b_image->id.name + 2;
}
bool BlenderImageLoader::equals(const ImageLoader &other) const
{
const BlenderImageLoader &other_loader = (const BlenderImageLoader &)other;
return b_image == other_loader.b_image && b_iuser.framenr == other_loader.b_iuser.framenr &&
b_iuser.tile == other_loader.b_iuser.tile &&
cached_update_count == other_loader.cached_update_count;
}
int BlenderImageLoader::get_tile_number() const
{
return b_iuser.tile;
}
void BlenderSession::builtin_images_load()
{
/* Force builtin images to be loaded along with Blender data sync. This
* is needed because we may be reading from depsgraph evaluated data which
* can be freed by Blender before Cycles reads it.
*
* TODO: the assumption that no further access to builtin image data will
* happen is really weak, and likely to break in the future. We should find
* a better solution to hand over the data directly to the image manager
* instead of through callbacks whose timing is difficult to control. */
ImageManager *manager = session->scene->image_manager.get();
Device *device = session->device.get();
manager->device_load_builtin(device, session->scene.get(), session->progress);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,42 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "DNA_image_types.h"
#include "scene/image_loader.h"
#include "scene/image_vdb.h"
#include "util/types.h"
struct Image;
struct ImageUser;
CCL_NAMESPACE_BEGIN
class BlenderImageLoader : public ImageLoader {
public:
BlenderImageLoader(blender::Image *b_image,
blender::ImageUser *b_iuser,
const int frame,
const int tile_number,
const bool is_preview_render);
bool load_metadata(ImageMetaData &metadata,
const ImageLoaderParams &params,
Progress &progress) override;
bool load_pixels(const ImageMetaData &metadata, void *pixels) override;
string name() const override;
bool equals(const ImageLoader &other) const override;
int get_tile_number() const override;
blender::Image *b_image;
blender::ImageUser b_iuser;
bool free_cache;
uint64_t cached_update_count;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "util/implicit_sharing.h"
#include "BLI_implicit_sharing.hh"
#include "blender/CCL_api.h"
namespace blender {
void CCL_implicit_sharing_init()
{
ccl::implicit_sharing_init(
[](ccl::ImplicitSharingInfo data) {
const auto *info = static_cast<const blender::ImplicitSharingInfo *>(data);
info->add_user();
},
[](ccl::ImplicitSharingInfo data) {
const auto *info = static_cast<const blender::ImplicitSharingInfo *>(data);
info->remove_user_and_delete_if_last();
});
}
} // namespace blender

View File

@@ -0,0 +1,155 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/light.h"
#include "DNA_light_types.h"
#include "DNA_world_types.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "scene/object.h"
CCL_NAMESPACE_BEGIN
void BlenderSync::sync_light(BObjectInfo &b_ob_info, Light *light)
{
blender::Light &b_light = *blender::id_cast<blender::Light *>(b_ob_info.object_data);
light->name = b_light.id.name + 2;
if (PointLight *point_light = dynamic_cast<PointLight *>(light)) {
point_light->set_radius(b_light.radius);
point_light->set_is_sphere(!(b_light.mode & blender::LA_USE_SOFT_FALLOFF));
}
else if (AreaLight *area_light = dynamic_cast<AreaLight *>(light)) {
area_light->set_sizeu(b_light.area_size);
area_light->set_spread(b_light.area_spread);
if (b_light.area_shape == blender::LA_AREA_SQUARE ||
b_light.area_shape == blender::LA_AREA_DISK)
{
area_light->set_sizev(area_light->get_sizeu());
}
else {
area_light->set_sizev(b_light.area_sizey);
}
area_light->set_ellipse(b_light.area_shape == blender::LA_AREA_DISK ||
b_light.area_shape == blender::LA_AREA_ELLIPSE);
}
else if (SunLight *sun_light = dynamic_cast<SunLight *>(light)) {
sun_light->set_angle(b_light.sun_angle);
}
if (SpotLight *spot_light = dynamic_cast<SpotLight *>(light)) {
spot_light->set_angle(b_light.spotsize);
spot_light->set_smooth(b_light.spotblend);
}
blender::PointerRNA light_rna_ptr = RNA_id_pointer_create(&b_light.id);
/* Color and strength. */
float3 light_color = make_float3(b_light.r, b_light.g, b_light.b);
if (b_light.mode & blender::LA_USE_TEMPERATURE) {
float color[3];
RNA_float_get_array(&light_rna_ptr, "temperature_color", color);
light_color *= make_float3(color[0], color[1], color[2]);
}
const float3 strength = light_color * b_light.energy * exp2f(b_light.exposure);
light->set_strength(strength);
/* normalize */
light->set_normalize(!(b_light.mode & blender::LA_UNNORMALIZED));
/* shadow */
blender::PointerRNA clight = RNA_pointer_get(&light_rna_ptr, "cycles");
light->set_cast_shadow(b_light.mode & blender::LA_SHADOW);
light->set_use_mis(get_boolean(clight, "use_multiple_importance_sampling"));
/* caustics light */
light->set_use_caustics(get_boolean(clight, "is_caustics_light"));
light->set_max_bounces(get_int(clight, "max_bounces"));
if (AreaLight *area_light = dynamic_cast<AreaLight *>(light)) {
area_light->set_is_portal(get_boolean(clight, "is_portal"));
}
/* tag */
light->tag_update(scene);
}
void BlenderSync::sync_background_light(blender::bScreen *b_screen, blender::View3D *b_v3d)
{
blender::World *b_world = view_layer.world_override ? view_layer.world_override : b_scene->world;
if (b_world) {
blender::PointerRNA world_rna_ptr = RNA_id_pointer_create(&b_world->id);
blender::PointerRNA cworld = RNA_pointer_get(&world_rna_ptr, "cycles");
enum SamplingMethod { SAMPLING_NONE = 0, SAMPLING_AUTOMATIC, SAMPLING_MANUAL, SAMPLING_NUM };
const int sampling_method = get_enum(
cworld, "sampling_method", SAMPLING_NUM, SAMPLING_AUTOMATIC);
const bool sample_as_light = (sampling_method != SAMPLING_NONE);
/* Create object. */
Object *object;
const ObjectKey object_key(b_world, nullptr, b_world, false);
bool update = object_map.add_or_update(&object, &b_world->id, &b_world->id, object_key);
if (update) {
/* Lights should be shadow catchers by default. */
object->set_is_shadow_catcher(true);
}
object->set_lightgroup(
ustring((b_world && b_world->lightgroup) ? b_world->lightgroup->name : ""));
object->set_asset_name(ustring(b_world->id.name + 2));
/* Create geometry. */
const GeometryKey geom_key{b_world, Geometry::BACKGROUND_LIGHT};
Geometry *geom = geometry_map.find(geom_key);
if (geom) {
update |= geometry_map.update(geom, &b_world->id);
}
else {
geom = scene->create_light_node<BackgroundLight>();
geometry_map.add(geom_key, geom);
object->set_geometry(geom);
update = true;
}
if (update || world_recalc || b_world != world_map) {
/* Initialize light geometry. */
BackgroundLight *light = static_cast<BackgroundLight *>(geom);
array<Node *> used_shaders;
used_shaders.push_back_slow(scene->default_background);
light->set_used_shaders(used_shaders);
if (sampling_method == SAMPLING_MANUAL) {
light->set_map_resolution(get_int(cworld, "sample_map_resolution"));
}
else {
light->set_map_resolution(0);
}
light->set_use_mis(sample_as_light);
light->set_max_bounces(get_int(cworld, "max_bounces"));
/* Caustic light. */
light->set_use_caustics(get_boolean(cworld, "is_caustics_light"));
light->set_cast_shadow(get_boolean(cworld, "use_shadows"));
light->tag_update(scene);
geometry_map.set_recalc(b_world);
}
}
world_map = b_world;
world_recalc = false;
viewport_parameters = BlenderViewportParameters(b_screen, b_v3d, use_developer_ui);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "blender/light_linking.h"
#include "DNA_object_types.h"
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
static const blender::LightLinking *get_light_linking(const blender::Object &b_object)
{
return b_object.light_linking;
}
uint64_t BlenderLightLink::get_light_set_membership(const blender::Object * /*parent*/,
const blender::Object &object)
{
const blender::LightLinking *light_linking = get_light_linking(object);
return (light_linking) ? light_linking->runtime.light_set_membership : LIGHT_LINK_MASK_ALL;
}
uint BlenderLightLink::get_receiver_light_set(const blender::Object *parent,
const blender::Object &object)
{
if (parent) {
const blender::LightLinking *parent_light_linking = get_light_linking(*parent);
if (parent_light_linking && parent_light_linking->runtime.receiver_light_set) {
return parent_light_linking->runtime.receiver_light_set;
}
}
const blender::LightLinking *light_linking = get_light_linking(object);
return (light_linking) ? light_linking->runtime.receiver_light_set : 0;
}
uint64_t BlenderLightLink::get_shadow_set_membership(const blender::Object * /*parent*/,
const blender::Object &object)
{
const blender::LightLinking *light_linking = get_light_linking(object);
return (light_linking) ? light_linking->runtime.shadow_set_membership : LIGHT_LINK_MASK_ALL;
}
uint BlenderLightLink::get_blocker_shadow_set(const blender::Object *parent,
const blender::Object &object)
{
if (parent) {
const blender::LightLinking *parent_light_linking = get_light_linking(*parent);
if (parent_light_linking && parent_light_linking->runtime.blocker_shadow_set) {
return parent_light_linking->runtime.blocker_shadow_set;
}
}
const blender::LightLinking *light_linking = get_light_linking(object);
return (light_linking) ? light_linking->runtime.blocker_shadow_set : 0;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/types_base.h"
#include <cstdint>
namespace blender {
struct Object;
}
CCL_NAMESPACE_BEGIN
class BlenderLightLink {
public:
static uint64_t get_light_set_membership(const blender::Object *parent,
const blender::Object &object);
static uint get_receiver_light_set(const blender::Object *parent, const blender::Object &object);
static uint64_t get_shadow_set_membership(const blender::Object *parent,
const blender::Object &object);
static uint get_blocker_shadow_set(const blender::Object *parent, const blender::Object &object);
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "blender/CCL_api.h"
#include "util/log.h"
#include "CLG_log.h"
namespace blender {
static CLG_LogRef LOG = {"cycles"};
void CCL_log_init()
{
/* Set callback to pass log messages to CLOG. */
ccl::log_init(
[](const ccl::LogLevel level, const char *file_line, const char *func, const char *msg) {
const CLG_LogType *log_type = CLOG_ENSURE(&LOG);
switch (level) {
case ccl::LOG_LEVEL_FATAL:
case ccl::LOG_LEVEL_DFATAL:
CLG_log_str(log_type, CLG_LEVEL_FATAL, file_line, func, msg);
return;
case ccl::LOG_LEVEL_ERROR:
case ccl::LOG_LEVEL_DERROR:
CLG_log_str(log_type, CLG_LEVEL_ERROR, file_line, func, msg);
return;
case ccl::LOG_LEVEL_WARNING:
case ccl::LOG_LEVEL_DWARNING:
CLG_log_str(log_type, CLG_LEVEL_WARN, file_line, func, msg);
return;
case ccl::LOG_LEVEL_INFO:
case ccl::LOG_LEVEL_INFO_IMPORTANT:
CLG_log_str(log_type, CLG_LEVEL_INFO, file_line, func, msg);
return;
case ccl::LOG_LEVEL_DEBUG:
CLG_log_str(log_type, CLG_LEVEL_DEBUG, file_line, func, msg);
return;
case ccl::LOG_LEVEL_TRACE:
case ccl::LOG_LEVEL_UNKNOWN:
CLG_log_str(log_type, CLG_LEVEL_TRACE, file_line, func, msg);
return;
}
});
/* Map log level from CLOG. */
const CLG_LogType *log_type = CLOG_ENSURE(&LOG);
switch (log_type->level) {
case CLG_LEVEL_FATAL:
ccl::log_level_set(ccl::LOG_LEVEL_FATAL);
break;
case CLG_LEVEL_ERROR:
ccl::log_level_set(ccl::LOG_LEVEL_ERROR);
break;
case CLG_LEVEL_WARN:
ccl::log_level_set(ccl::LOG_LEVEL_WARNING);
break;
case CLG_LEVEL_INFO:
ccl::log_level_set(ccl::LOG_LEVEL_INFO);
break;
case CLG_LEVEL_DEBUG:
ccl::log_level_set(ccl::LOG_LEVEL_DEBUG);
break;
case CLG_LEVEL_TRACE:
ccl::log_level_set(ccl::LOG_LEVEL_TRACE);
break;
}
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,700 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "blender/light_linking.h"
#include "blender/object_cull.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "scene/camera.h"
#include "scene/integrator.h"
#include "scene/light.h"
#include "scene/mesh.h"
#include "scene/object.h"
#include "scene/particles.h"
#include "scene/scene.h"
#include "scene/shader.h"
#include "scene/shader_graph.h"
#include "scene/shader_nodes.h"
#include "util/hash.h"
#include "util/log.h"
#include "util/task.h"
#include "BKE_duplilist.hh"
#include "BKE_layer.hh"
#include "BKE_material.hh"
#include "BKE_object.hh"
#include "DEG_depsgraph_query.hh"
#include "RE_engine.h"
using blender::Object;
CCL_NAMESPACE_BEGIN
/* Utilities */
bool BlenderSync::BKE_object_is_modified(blender::Object &b_ob)
{
/* test if we can instance or if the object is modified */
if (b_ob.type == blender::OB_MBALL) {
/* Multi-user and dupli meta-balls are fused, can't instance. */
return true;
}
const int settings = preview ? blender::eModifierMode_Realtime : blender::eModifierMode_Render;
if ((blender::BKE_object_is_modified(b_scene, &b_ob) & settings) != 0) {
/* modifiers */
return true;
}
/* Object level material links. Note the geometry material slot array may not match
* the object matbits array, so we need to guard against out of bounds. */
for (const int i : blender::IndexRange(BKE_object_material_count_eval(&b_ob))) {
if (i < b_ob.totcol && b_ob.matbits && b_ob.matbits[i] != 0) {
return true;
}
}
return false;
}
bool BlenderSync::object_is_geometry(BObjectInfo &b_ob_info)
{
blender::ID *b_ob_data = b_ob_info.object_data;
if (!b_ob_data) {
return false;
}
const blender::ObjectType type = b_ob_info.iter_object->type;
if (type == blender::OB_VOLUME || type == blender::OB_CURVES || type == blender::OB_POINTCLOUD ||
type == blender::OB_LAMP)
{
/* Will be exported as geometry. */
return true;
}
return GS(b_ob_data->name) == blender::ID_ME;
}
bool BlenderSync::object_can_have_geometry(blender::Object &b_ob)
{
const blender::ObjectType type = b_ob.type;
switch (type) {
case blender::OB_MESH:
case blender::OB_CURVES_LEGACY:
case blender::OB_SURF:
case blender::OB_MBALL:
case blender::OB_FONT:
case blender::OB_CURVES:
case blender::OB_POINTCLOUD:
case blender::OB_VOLUME:
/* TODO(weizhen): OB_LAMP */
return true;
default:
return false;
}
}
bool BlenderSync::object_is_light(blender::Object &b_ob)
{
blender::ID *b_ob_data = object_get_data(b_ob, true);
return (b_ob_data && GS(b_ob_data->name) == blender::ID_LA);
}
bool BlenderSync::object_is_camera(blender::Object &b_ob)
{
blender::ID *b_ob_data = object_get_data(b_ob, true);
return (b_ob_data && GS(b_ob_data->name) == blender::ID_CA);
}
void BlenderSync::sync_object_motion_init(blender::Object &b_parent,
blender::Object &b_ob,
Object *object)
{
/* Initialize motion blur for object, detecting if it's enabled and creating motion
* steps array if so. */
array<Transform> motion = object->get_motion();
Geometry *geom = object->get_geometry();
if (!geom) {
return;
}
int motion_steps = 0;
bool use_motion_blur = false;
const Scene::MotionType need_motion = scene->need_motion();
if (need_motion == Scene::MOTION_BLUR) {
motion_steps = object_motion_steps(b_parent, b_ob, Object::MAX_MOTION_STEPS);
if (motion_steps && object_use_deform_motion(b_parent, b_ob)) {
use_motion_blur = true;
}
}
else if (need_motion != Scene::MOTION_NONE) {
motion_steps = 3;
}
geom->set_use_motion_blur(use_motion_blur);
motion.resize(motion_steps, transform_empty());
if (motion_steps) {
motion[motion_steps / 2] = object->get_tfm();
/* update motion socket before trying to access object->motion_time */
object->set_motion(motion);
for (size_t step = 0; step < motion_steps; step++) {
motion_times.insert(object->motion_time(step));
}
}
else {
object->set_motion(motion);
}
}
Object *BlenderSync::sync_object(blender::ViewLayer &b_view_layer,
blender::Object &b_ob,
blender::DEGObjectIterData &b_deg_iter_data,
const float motion_time,
bool use_particle_hair,
bool show_lights,
BlenderObjectCulling &culling,
TaskPool *geom_task_pool)
{
const bool is_instance = b_deg_iter_data.dupli_object_current;
blender::Object *b_parent = is_instance ? b_deg_iter_data.dupli_parent : &b_ob;
blender::Object *b_real_object = is_instance ? b_deg_iter_data.dupli_object_current->ob : &b_ob;
const bool use_adaptive_subdiv = object_subdivision_type(
*b_real_object, preview, use_adaptive_subdivision) !=
Mesh::SUBDIVISION_NONE;
BObjectInfo b_ob_info{
&b_ob, b_real_object, object_get_data(b_ob, use_adaptive_subdiv), use_adaptive_subdiv};
const bool motion = motion_time != 0.0f;
const Transform tfm = get_transform(b_ob.object_to_world());
const int *persistent_id = nullptr;
if (is_instance) {
persistent_id = b_deg_iter_data.dupli_object_current->persistent_id;
if (!motion && !b_ob_info.is_real_object_data()) {
/* Remember which object data the geometry is coming from, so that we can sync it when the
* object has changed. */
instance_geometries_by_object[b_ob_info.real_object].insert(b_ob_info.object_data);
}
}
/* only interested in object that we can create geometry from */
if (!object_is_geometry(b_ob_info)) {
return nullptr;
}
/* Perform object culling. */
if (object_is_light(b_ob)) {
if (!show_lights) {
return nullptr;
}
}
else if (culling.test(scene, b_ob, tfm)) {
return nullptr;
}
/* Visibility flags for both parent and child. */
blender::PointerRNA b_ob_rna_ptr = RNA_id_pointer_create(&b_ob.id);
blender::PointerRNA cobject = RNA_pointer_get(&b_ob_rna_ptr, "cycles");
/* Note base_parent is null for objects from the background scene. */
const blender::Base *base_parent = BKE_view_layer_base_find(&b_view_layer, b_parent);
const bool use_holdout = (base_parent && (base_parent->flag & blender::BASE_HOLDOUT) != 0) ||
((b_parent->visibility_flag & blender::OB_HOLDOUT) != 0);
PathRayVisibility visibility = object_ray_visibility(b_ob);
if (b_parent != &b_ob) {
visibility &= object_ray_visibility(*b_parent);
}
/* TODO: make holdout objects on excluded layer invisible for non-camera rays. */
#if 0
if (use_holdout && (layer_flag & view_layer.exclude_layer)) {
visibility &= ~(PATH_RAY_VISIBILITY_ALL & ~PATH_RAY_VISIBILITY_CAMERA);
}
#endif
/* Clear camera visibility for indirect only objects. */
const bool use_indirect_only = !use_holdout && base_parent &&
((base_parent->flag & blender::BASE_INDIRECT_ONLY) != 0);
if (use_indirect_only) {
visibility &= ~PATH_RAY_VISIBILITY_CAMERA;
}
/* Don't export completely invisible objects. */
if (visibility == PATH_RAY_VISIBILITY_NONE) {
return nullptr;
}
/* Use task pool only for non-instances, since sync_dupli_particle accesses
* geometry. This restriction should be removed for better performance. */
TaskPool *object_geom_task_pool = (is_instance) ? nullptr : geom_task_pool;
/* key to lookup object */
const ObjectKey key(b_parent, persistent_id, b_ob_info.real_object, use_particle_hair);
Object *object;
/* motion vector case */
if (motion) {
object = object_map.find(key);
if (object && object->use_motion()) {
/* Set transform at matching motion time step. */
const int time_index = object->motion_step(motion_time);
if (time_index >= 0) {
object->set_motion_tfm(tfm, time_index);
}
/* mesh deformation */
if (object->get_geometry()) {
sync_geometry_motion(
b_ob_info, object, motion_time, use_particle_hair, object_geom_task_pool);
}
}
return object;
}
/* test if we need to sync */
bool object_updated = object_map.add_or_update(&object, &b_ob.id, &b_parent->id, key) ||
!object->tfm_equals(tfm);
/* mesh sync */
Geometry *geometry = sync_geometry(
b_ob_info, object_updated, use_particle_hair, object_geom_task_pool);
object->set_geometry(geometry);
/* special case not tracked by object update flags */
if (sync_object_attributes(b_ob, b_deg_iter_data, object)) {
object_updated = true;
}
/* holdout */
object->set_use_holdout(use_holdout);
object->set_visibility(visibility);
object->set_is_shadow_catcher((b_ob.visibility_flag & blender::OB_SHADOW_CATCHER) != 0 ||
(b_parent->visibility_flag & blender::OB_SHADOW_CATCHER) != 0);
object->set_shadow_terminator_shading_offset(b_ob.shadow_terminator_shading_offset);
object->set_shadow_terminator_geometry_offset(b_ob.shadow_terminator_geometry_offset);
float ao_distance = get_float(cobject, "ao_distance");
if (ao_distance == 0.0f && b_parent != &b_ob) {
blender::PointerRNA b_parent_rna_ptr = RNA_id_pointer_create(&b_parent->id);
blender::PointerRNA cparent = RNA_pointer_get(&b_parent_rna_ptr, "cycles");
ao_distance = get_float(cparent, "ao_distance");
}
object->set_ao_distance(ao_distance);
const bool is_caustics_caster = get_boolean(cobject, "is_caustics_caster");
object->set_is_caustics_caster(is_caustics_caster);
const bool is_caustics_receiver = get_boolean(cobject, "is_caustics_receiver");
object->set_is_caustics_receiver(is_caustics_receiver);
object->set_is_bake_target(b_ob_info.real_object == b_bake_target);
/* sync the asset name for Cryptomatte */
blender::Object *parent = b_ob.parent;
ustring parent_name;
if (parent) {
while (parent->parent) {
parent = parent->parent;
}
parent_name = BKE_id_name(parent->id);
}
else {
parent_name = BKE_id_name(b_ob.id);
}
object->set_asset_name(parent_name);
/* object sync
* transform comparison should not be needed, but duplis don't work perfect
* in the depsgraph and may not signal changes, so this is a workaround */
const bool do_sync = object->is_modified() || object_updated ||
(object->get_geometry() && object->get_geometry()->is_modified());
if (do_sync) {
object->name = BKE_id_name(b_ob.id);
object->set_pass_id(b_ob.index);
const float *object_color = b_ob.color;
object->set_color(make_float3(object_color[0], object_color[1], object_color[2]));
object->set_alpha(object_color[3]);
object->set_tfm(tfm);
/* dupli texture coordinates and random_id */
if (is_instance) {
const float *orco = b_deg_iter_data.dupli_object_current->orco;
object->set_dupli_generated(0.5f * make_float3(orco[0], orco[1], orco[2]) -
make_float3(0.5f, 0.5f, 0.5f));
const float *uv = b_deg_iter_data.dupli_object_current->uv;
object->set_dupli_uv(make_float2(uv[0], uv[1]));
object->set_random_id(b_deg_iter_data.dupli_object_current->random_id);
}
else {
object->set_dupli_generated(zero_float3());
object->set_dupli_uv(zero_float2());
object->set_random_id(hash_uint2(hash_string(object->name.c_str()), 0));
}
/* Light group and linking. */
string lightgroup = b_ob.lightgroup ? b_ob.lightgroup->name : "";
if (lightgroup.empty()) {
lightgroup = b_parent->lightgroup ? b_parent->lightgroup->name : "";
}
object->set_lightgroup(ustring(lightgroup));
object->set_light_set_membership(BlenderLightLink::get_light_set_membership(b_parent, b_ob));
object->set_receiver_light_set(BlenderLightLink::get_receiver_light_set(b_parent, b_ob));
object->set_shadow_set_membership(BlenderLightLink::get_shadow_set_membership(b_parent, b_ob));
object->set_blocker_shadow_set(BlenderLightLink::get_blocker_shadow_set(b_parent, b_ob));
}
sync_object_motion_init(*b_parent, b_ob, object);
if (do_sync || object->motion_is_modified()) {
object->tag_update(scene);
}
if (is_instance) {
/* Sync possible particle data. */
sync_dupli_particle(*b_parent, b_deg_iter_data, b_ob, object);
}
return object;
}
static float4 lookup_instance_property(blender::Object &ob,
blender::DEGObjectIterData &b_deg_iter_data,
const string &name,
bool use_instancer)
{
blender::DupliObject *dupli = nullptr;
blender::Object *dupli_parent = nullptr;
/* If requesting instance data, check the parent particle system and object. */
if (use_instancer && b_deg_iter_data.dupli_object_current) {
dupli = b_deg_iter_data.dupli_object_current;
dupli_parent = b_deg_iter_data.dupli_parent;
}
float4 value;
BKE_object_dupli_find_rgba_attribute(&ob, dupli, dupli_parent, name.c_str(), &value.x);
return value;
}
bool BlenderSync::sync_object_attributes(blender::Object &b_ob,
blender::DEGObjectIterData &b_deg_iter_data,
Object *object)
{
/* Find which attributes are needed. */
AttributeRequestSet requests = object->get_geometry()->needed_attributes();
/* Delete attributes that became unnecessary. */
vector<ParamValue> &attributes = object->attributes;
bool changed = false;
for (int i = attributes.size() - 1; i >= 0; i--) {
if (!requests.find(attributes[i].name())) {
attributes.erase(attributes.begin() + i);
changed = true;
}
}
/* Update attribute values. */
for (const AttributeRequest &req : requests.requests) {
const ustring name = req.name;
std::string real_name;
const int type = blender_attribute_name_split_type(name, &real_name);
if (type == blender::SHD_ATTRIBUTE_OBJECT || type == blender::SHD_ATTRIBUTE_INSTANCER) {
const bool use_instancer = (type == blender::SHD_ATTRIBUTE_INSTANCER);
float4 value = lookup_instance_property(b_ob, b_deg_iter_data, real_name, use_instancer);
/* Try finding the existing attribute value. */
ParamValue *param = nullptr;
for (size_t i = 0; i < attributes.size(); i++) {
if (attributes[i].name() == name) {
param = &attributes[i];
break;
}
}
/* Replace or add the value. */
const ParamValue new_param(name, TypeFloat4, 1, &value);
assert(new_param.datasize() == sizeof(value));
if (!param) {
changed = true;
attributes.push_back(new_param);
}
else {
/* Cannot use param->get<float4>, ParamValue storage is not guaranteed to be aligned. */
const float *param_data = static_cast<const float *>(param->data());
if (make_float4(param_data[0], param_data[1], param_data[2], param_data[3]) != value) {
changed = true;
*param = new_param;
}
}
}
}
return changed;
}
/* Object Loop */
void BlenderSync::sync_objects(blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
const float motion_time)
{
/* Task pool for multithreaded geometry sync. */
TaskPool geom_task_pool;
/* layer data */
const bool motion = motion_time != 0.0f;
if (!motion) {
/* prepare for sync */
geometry_map.pre_sync();
object_map.pre_sync();
procedural_map.pre_sync();
particle_system_map.pre_sync();
motion_times.clear();
}
else {
geometry_motion_synced.clear();
}
if (!motion) {
/* Object to geometry instance mapping is built for the reference time, as other
* times just look up the corresponding geometry. */
instance_geometries_by_object.clear();
}
/* initialize culling */
BlenderObjectCulling culling(scene, *b_scene);
/* object loop */
bool cancel = false;
const bool show_lights =
BlenderViewportParameters(b_screen, b_v3d, use_developer_ui).use_scene_lights;
blender::ViewLayer &b_view_layer = *DEG_get_evaluated_view_layer(&b_depsgraph);
BKE_view_layer_synced_ensure(*b_data, b_scene, &b_view_layer);
blender::DEGObjectIterSettings deg_iter_settings{};
deg_iter_settings.depsgraph = &b_depsgraph;
deg_iter_settings.flags = DEG_OBJECT_ITER_FOR_RENDER_ENGINE_FLAGS;
blender::DEGObjectIterData deg_iter_data{};
deg_iter_data.settings = &deg_iter_settings;
deg_iter_data.graph = deg_iter_settings.depsgraph;
deg_iter_data.flag = deg_iter_settings.flags;
ITER_BEGIN (blender::DEG_iterator_objects_begin,
blender::DEG_iterator_objects_next,
blender::DEG_iterator_objects_end,
&deg_iter_data,
blender::Object *,
b_ob)
{
/* Viewport visibility. */
const bool show_in_viewport = !b_v3d || BKE_object_is_visible_in_viewport(b_v3d, b_ob);
if (show_in_viewport == false) {
continue;
}
/* Load per-object culling data. */
culling.init_object(scene, *b_ob);
const int ob_visibility = BKE_object_visibility(b_ob, deg_iter_data.eval_mode);
/* Ensure the object geom supporting the hair is processed before adding
* the hair processing task to the task pool, calling .to_mesh() on the
* same object in parallel does not work. */
const bool sync_hair = (ob_visibility & blender::OB_VISIBLE_PARTICLES) != 0 &&
object_has_particle_hair(b_ob);
/* Object itself. */
if ((ob_visibility & blender::OB_VISIBLE_SELF) != 0) {
sync_object(b_view_layer,
*b_ob,
deg_iter_data,
motion_time,
false,
show_lights,
culling,
sync_hair ? nullptr : &geom_task_pool);
}
/* Particle hair as separate object. */
if (sync_hair) {
sync_object(b_view_layer,
*b_ob,
deg_iter_data,
motion_time,
true,
show_lights,
culling,
&geom_task_pool);
}
cancel = progress.get_cancel();
if (cancel) {
break;
}
}
ITER_END;
geom_task_pool.wait_work();
progress.set_sync_status("");
if (!cancel && !motion) {
/* After object for world_use_portal. */
sync_background_light(b_screen, b_v3d);
/* Handle removed data and modified pointers, as this may free memory, delete Nodes in the
* right order to ensure that dependent data is freed after their users. Objects should be
* freed before particle systems and geometries. */
object_map.post_sync();
geometry_map.post_sync();
particle_system_map.post_sync();
procedural_map.post_sync();
}
if (motion) {
geometry_motion_synced.clear();
}
}
void BlenderSync::sync_objects_and_motion(blender::RenderData &b_render,
blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d,
const int width,
const int height,
void **python_thread_state)
{
/* get camera object here to deal with camera switch */
blender::Object *b_cam = get_camera_object(b_v3d, b_rv3d);
const int frame_center = b_scene->r.cfra;
const float subframe_center = b_scene->r.subframe;
float frame_center_delta = 0.0f;
if (scene->need_motion() == Scene::MOTION_BLUR &&
scene->camera->get_motion_position() != MOTION_POSITION_CENTER)
{
const float shuttertime = scene->camera->get_shuttertime();
if (scene->camera->get_motion_position() == MOTION_POSITION_END) {
frame_center_delta = -shuttertime * 0.5f;
}
else {
assert(scene->camera->get_motion_position() == MOTION_POSITION_START);
frame_center_delta = shuttertime * 0.5f;
}
const float time = frame_center + subframe_center + frame_center_delta;
const int frame = (int)floorf(time);
const float subframe = time - frame;
python_thread_state_restore(python_thread_state);
RE_engine_frame_set(b_engine, frame, subframe);
python_thread_state_save(python_thread_state);
if (b_cam) {
sync_camera_motion(b_render, b_cam, width, height, 0.0f);
}
}
sync_objects(b_depsgraph, b_screen, b_v3d);
/* In the viewport, only motion between previous frame and current frame is of interest, which is
* kept updated separately. */
if (b_v3d) {
assert(scene->need_motion() == Scene::MOTION_NONE ||
scene->need_motion() == Scene::MOTION_PASS_INTERACTIVE);
return;
}
if (scene->need_motion() == Scene::MOTION_NONE) {
return;
}
/* Insert motion times from camera. Motion times from other objects
* have already been added in a sync_objects call. */
if (b_cam) {
const uint camera_motion_steps = object_motion_steps(*b_cam, *b_cam);
for (size_t step = 0; step < camera_motion_steps; step++) {
motion_times.insert(scene->camera->motion_time(step));
}
}
/* Check which geometry already has motion blur so it can be skipped. */
geometry_motion_attribute_synced.clear();
for (Geometry *geom : scene->geometry) {
const Attribute *attr_P = geom->attributes.find(ATTR_STD_POSITION);
if (attr_P && attr_P->has_motion()) {
geometry_motion_attribute_synced.insert(geom);
}
}
/* note iteration over motion_times set happens in sorted order */
for (const float relative_time : motion_times) {
/* center time is already handled. */
if (relative_time == 0.0f) {
continue;
}
LOG_DEBUG << "Synchronizing motion for the relative time " << relative_time << ".";
/* fixed shutter time to get previous and next frame for motion pass */
const float shuttertime = scene->motion_shutter_time();
/* compute frame and subframe time */
const float time = frame_center + subframe_center + frame_center_delta +
relative_time * shuttertime * 0.5f;
const int frame = (int)floorf(time);
const float subframe = time - frame;
/* change frame */
python_thread_state_restore(python_thread_state);
RE_engine_frame_set(b_engine, frame, subframe);
python_thread_state_save(python_thread_state);
/* Syncs camera motion if relative_time is one of the camera's motion times. */
sync_camera_motion(b_render, b_cam, width, height, relative_time);
/* sync object */
sync_objects(b_depsgraph, b_screen, b_v3d, relative_time);
}
geometry_motion_attribute_synced.clear();
/* we need to set the python thread state again because this
* function assumes it is being executed from python and will
* try to save the thread state */
python_thread_state_restore(python_thread_state);
RE_engine_frame_set(b_engine, frame_center, subframe_center);
python_thread_state_save(python_thread_state);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,144 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include <cstdlib>
#include "scene/camera.h"
#include "BLI_bounds.hh"
#include "blender/object_cull.h"
#include "blender/util.h"
CCL_NAMESPACE_BEGIN
BlenderObjectCulling::BlenderObjectCulling(Scene *scene, blender::Scene &b_scene)
: use_scene_camera_cull_(false),
use_camera_cull_(false),
camera_cull_margin_(0.0f),
use_scene_distance_cull_(false),
use_distance_cull_(false),
distance_cull_margin_(0.0f)
{
if ((b_scene.r.mode & blender::R_SIMPLIFY) != 0) {
blender::PointerRNA scene_rna_ptr = RNA_id_pointer_create(&b_scene.id);
blender::PointerRNA cscene = RNA_pointer_get(&scene_rna_ptr, "cycles");
const bool cam_supported = (scene->camera->get_camera_type() == CAMERA_PERSPECTIVE) ||
(scene->camera->get_camera_type() == CAMERA_ORTHOGRAPHIC);
use_scene_camera_cull_ = cam_supported && ((b_scene.r.scemode & blender::R_MULTIVIEW) == 0) &&
get_boolean(cscene, "use_camera_cull");
use_scene_distance_cull_ = cam_supported &&
((b_scene.r.scemode & blender::R_MULTIVIEW) == 0) &&
get_boolean(cscene, "use_distance_cull");
camera_cull_margin_ = get_float(cscene, "camera_cull_margin");
distance_cull_margin_ = get_float(cscene, "distance_cull_margin");
if (distance_cull_margin_ == 0.0f) {
use_scene_distance_cull_ = false;
}
}
}
void BlenderObjectCulling::init_object(Scene *scene, blender::Object &b_ob)
{
if (!use_scene_camera_cull_ && !use_scene_distance_cull_) {
return;
}
blender::PointerRNA b_ob_rna_ptr = RNA_id_pointer_create(&b_ob.id);
blender::PointerRNA cobject = RNA_pointer_get(&b_ob_rna_ptr, "cycles");
use_camera_cull_ = use_scene_camera_cull_ && get_boolean(cobject, "use_camera_cull");
use_distance_cull_ = use_scene_distance_cull_ && get_boolean(cobject, "use_distance_cull");
if (use_camera_cull_ || use_distance_cull_) {
/* Need to have proper projection matrix. */
scene->camera->update(scene);
}
}
bool BlenderObjectCulling::test(Scene *scene, blender::Object &b_ob, const Transform &tfm)
{
if (!use_camera_cull_ && !use_distance_cull_) {
return false;
}
/* Compute world space bounding box corners. */
float3 bb[8];
std::array<blender::float3, 8> boundbox;
if (const std::optional<blender::Bounds<blender::float3>> bounds =
BKE_object_boundbox_eval_cached_get(&b_ob))
{
boundbox = blender::bounds::corners(*bounds);
}
else {
boundbox.fill(blender::float3(0));
}
for (int i = 0; i < 8; ++i) {
const float3 p = make_float3(boundbox[i].x, boundbox[i].y, boundbox[i].z);
bb[i] = transform_point(&tfm, p);
}
const bool camera_culled = use_camera_cull_ && test_camera(scene, bb);
const bool distance_culled = use_distance_cull_ && test_distance(scene, bb);
return ((camera_culled && distance_culled) || (camera_culled && !use_distance_cull_) ||
(distance_culled && !use_camera_cull_));
}
/* TODO(sergey): Not really optimal, consider approaches based on k-DOP in order
* to reduce number of objects which are wrongly considered visible.
*/
bool BlenderObjectCulling::test_camera(Scene *scene, const float3 bb[8])
{
Camera *cam = scene->camera;
const ProjectionTransform &worldtondc = cam->worldtondc;
float3 bb_min = make_float3(FLT_MAX, FLT_MAX, FLT_MAX);
float3 bb_max = make_float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
bool all_behind = true;
for (int i = 0; i < 8; ++i) {
float3 p = bb[i];
const float4 b = make_float4(p, 1.0f);
const float4 c = make_float4(
dot(worldtondc.x, b), dot(worldtondc.y, b), dot(worldtondc.z, b), dot(worldtondc.w, b));
p = make_float3(c / c.w);
if (c.z < 0.0f) {
p.x = 1.0f - p.x;
p.y = 1.0f - p.y;
}
if (c.z >= -camera_cull_margin_) {
all_behind = false;
}
bb_min = min(bb_min, p);
bb_max = max(bb_max, p);
}
if (all_behind) {
return true;
}
return (bb_min.x >= 1.0f + camera_cull_margin_ || bb_min.y >= 1.0f + camera_cull_margin_ ||
bb_max.x <= -camera_cull_margin_ || bb_max.y <= -camera_cull_margin_);
}
bool BlenderObjectCulling::test_distance(Scene *scene, const float3 bb[8])
{
const float3 camera_position = transform_get_column(&scene->camera->get_matrix(), 3);
float3 bb_min = make_float3(FLT_MAX, FLT_MAX, FLT_MAX);
float3 bb_max = make_float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
/* Find min & max points for x & y & z on bounding box */
for (int i = 0; i < 8; ++i) {
const float3 p = bb[i];
bb_min = min(bb_min, p);
bb_max = max(bb_max, p);
}
const float3 closest_point = max(min(bb_max, camera_position), bb_min);
return (len_squared(camera_position - closest_point) >
distance_cull_margin_ * distance_cull_margin_);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "blender/sync.h"
#include "util/types.h"
CCL_NAMESPACE_BEGIN
class Scene;
class BlenderObjectCulling {
public:
BlenderObjectCulling(Scene *scene, blender::Scene &b_scene);
void init_object(Scene *scene, blender::Object &b_ob);
bool test(Scene *scene, blender::Object &b_ob, const Transform &tfm);
private:
bool test_camera(Scene *scene, const float3 bb[8]);
bool test_distance(Scene *scene, const float3 bb[8]);
bool use_scene_camera_cull_;
bool use_camera_cull_;
float camera_cull_margin_;
bool use_scene_distance_cull_;
bool use_distance_cull_;
float distance_cull_margin_;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "blender/output_driver.h"
#include "BLI_listbase.h"
#include "IMB_imbuf_types.hh"
#include "RE_engine.h"
CCL_NAMESPACE_BEGIN
BlenderOutputDriver::BlenderOutputDriver(blender::RenderEngine &b_engine) : b_engine_(b_engine) {}
BlenderOutputDriver::~BlenderOutputDriver() = default;
bool BlenderOutputDriver::read_render_tile(const Tile &tile)
{
/* Get render result. */
blender::RenderResult *b_rr = RE_engine_begin_result(&b_engine_,
tile.offset.x,
tile.offset.y,
tile.size.x,
tile.size.y,
tile.layer.c_str(),
tile.view.c_str());
/* Can happen if the intersected rectangle gives 0 width or height. */
if (b_rr == nullptr) {
return false;
}
/* layer will be missing if it was disabled in the UI */
if (b_rr->layers.is_empty()) {
return false;
}
blender::RenderLayer *b_rlay = static_cast<blender::RenderLayer *>(b_rr->layers.first);
/* Copy each pass.
* TODO:copy only the required ones for better performance? */
for (blender::RenderPass &b_pass : b_rlay->passes) {
if (b_pass.ibuf && b_pass.ibuf->float_data()) {
const float *rect = b_pass.ibuf->float_data();
tile.set_pass_pixels(b_pass.name, b_pass.channels, rect);
}
else {
blender::Array<float> rect(int64_t(b_pass.channels) * b_pass.rectx * b_pass.recty, 0.0f);
tile.set_pass_pixels(b_pass.name, b_pass.channels, rect.data());
}
}
RE_engine_end_result(&b_engine_, b_rr, false, false, false);
return true;
}
bool BlenderOutputDriver::update_render_tile(const Tile &tile)
{
/* Use final write for preview renders, otherwise render result wouldn't be updated
* quickly on Blender side. For all other cases we use the display driver. */
if (b_engine_.flag & blender::RE_ENGINE_PREVIEW) {
write_render_tile(tile);
return true;
}
/* Don't highlight full-frame tile. */
if (!(tile.size == tile.full_size)) {
RE_engine_tile_highlight_clear_all(&b_engine_);
RE_engine_tile_highlight_set(
&b_engine_, tile.offset.x, tile.offset.y, tile.size.x, tile.size.y, true);
}
return false;
}
void BlenderOutputDriver::write_render_tile(const Tile &tile)
{
RE_engine_tile_highlight_clear_all(&b_engine_);
/* Get render result. */
blender::RenderResult *b_rr = RE_engine_begin_result(&b_engine_,
tile.offset.x,
tile.offset.y,
tile.size.x,
tile.size.y,
tile.layer.c_str(),
tile.view.c_str());
/* Can happen if the intersected rectangle gives 0 width or height. */
if (b_rr == nullptr) {
return;
}
/* Layer will be missing if it was disabled in the UI. */
if (b_rr->layers.is_empty()) {
return;
}
blender::RenderLayer *b_rlay = static_cast<blender::RenderLayer *>(b_rr->layers.first);
vector<float> pixels(static_cast<size_t>(tile.size.x) * tile.size.y * 4);
/* Copy each pass. */
for (blender::RenderPass &b_pass : b_rlay->passes) {
if (!tile.get_pass_pixels(b_pass.name, b_pass.channels, pixels.data())) {
memset(pixels.data(), 0, pixels.size() * sizeof(float));
}
if (b_pass.ibuf && b_pass.ibuf->float_data()) {
float *rect = b_pass.ibuf->float_data_for_write();
const size_t size_in_bytes = sizeof(float) * b_pass.rectx * b_pass.recty * b_pass.channels;
memcpy(rect, pixels.data(), size_in_bytes);
}
}
RE_engine_end_result(&b_engine_, b_rr, false, false, true);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "session/output_driver.h"
namespace blender {
struct RenderEngine;
}
CCL_NAMESPACE_BEGIN
class BlenderOutputDriver : public OutputDriver {
public:
explicit BlenderOutputDriver(blender::RenderEngine &b_engine);
~BlenderOutputDriver() override;
void write_render_tile(const Tile &tile) override;
bool update_render_tile(const Tile &tile) override;
bool read_render_tile(const Tile &tile) override;
protected:
blender::RenderEngine &b_engine_;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/particles.h"
#include "scene/mesh.h"
#include "scene/object.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "DNA_particle_types.h"
#include "BKE_scene.hh"
#include "DEG_depsgraph_query.hh"
CCL_NAMESPACE_BEGIN
/* Utilities */
bool BlenderSync::sync_dupli_particle(blender::Object &b_parent,
blender::DEGObjectIterData &b_deg_iter_data,
blender::Object &b_ob,
Object *object)
{
/* Test if this dupli was generated from a particle system. */
blender::ParticleSystem *b_psys = b_deg_iter_data.dupli_object_current->particle_system;
if (!b_psys) {
return false;
}
object->set_hide_on_missing_motion(true);
/* test if we need particle data */
if (!object->get_geometry()->need_attribute(scene, ATTR_STD_PARTICLE)) {
return false;
}
/* don't handle child particles yet */
const int *persistent_id = b_deg_iter_data.dupli_object_current->persistent_id;
if (persistent_id[0] >= b_psys->totpart) {
return false;
}
/* find particle system */
const ParticleSystemKey key(&b_parent, persistent_id);
ParticleSystem *psys;
const bool first_use = !particle_system_map.is_used(key);
const bool need_update = particle_system_map.add_or_update(&psys, &b_parent.id, &b_ob.id, key);
/* no update needed? */
if (!need_update && !object->get_geometry()->is_modified() &&
!scene->object_manager->need_update())
{
return true;
}
/* first time used in this sync loop? clear and tag update */
if (first_use) {
psys->particles.clear();
psys->tag_update(scene);
}
/* add particle */
blender::ParticleData &b_pa = b_psys->particles[persistent_id[0]];
Particle pa;
pa.index = persistent_id[0];
pa.age = BKE_scene_frame_to_ctime(b_scene, b_scene->r.cfra) - b_pa.time;
pa.lifetime = b_pa.lifetime;
pa.location = make_float3(b_pa.state.co[0], b_pa.state.co[1], b_pa.state.co[2]);
pa.rotation = make_float4(
b_pa.state.rot[0], b_pa.state.rot[1], b_pa.state.rot[2], b_pa.state.rot[3]);
pa.size = b_pa.size;
pa.velocity = make_float3(b_pa.state.vel[0], b_pa.state.vel[1], b_pa.state.vel[2]);
pa.angular_velocity = make_float3(b_pa.state.ave[0], b_pa.state.ave[1], b_pa.state.ave[2]);
psys->particles.push_back_slow(pa);
object->set_particle_system(psys);
object->set_particle_index(psys->particles.size() - 1);
if (object->particle_index_is_modified()) {
scene->object_manager->tag_update(scene, ObjectManager::PARTICLE_MODIFIED);
}
/* return that this object has particle data */
return true;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,318 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/pointcloud.h"
#include "scene/attribute.h"
#include "scene/scene.h"
#include "util/hash.h"
#include "blender/attribute_convert.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
CCL_NAMESPACE_BEGIN
static void attr_create_motion_from_velocity(PointCloud *pointcloud,
const blender::Span<blender::float3> b_attribute,
const float motion_scale)
{
const int num_points = pointcloud->num_points();
/* Override motion steps to fixed number. */
pointcloud->set_motion_steps(3);
/* Set motion steps on position and radius attributes. */
Attribute *attr_P = pointcloud->attributes.find(ATTR_STD_POSITION);
Attribute *attr_R = pointcloud->attributes.find(ATTR_STD_RADIUS);
attr_P->add_motion(pointcloud);
attr_R->add_motion(pointcloud);
const packed_float3 *P = pointcloud->get_position();
const float *radius = pointcloud->get_radius();
/* Only export previous and next frame, we don't have any in between data. */
const float motion_times[2] = {-1.0f, 1.0f};
for (int step = 1; step <= 2; step++) {
const float relative_time = motion_times[step - 1] * 0.5f * motion_scale;
packed_float3 *mP = attr_P->data_for_write<packed_float3>(step);
float *mR = attr_R->data_for_write<float>(step);
for (int i = 0; i < num_points; i++) {
mP[i] = float3(P[i]) +
make_float3(b_attribute[i][0], b_attribute[i][1], b_attribute[i][2]) * relative_time;
mR[i] = radius[i];
}
}
}
static void copy_attributes(PointCloud *pointcloud,
const blender::PointCloud &b_pointcloud,
const bool need_motion,
const float motion_scale)
{
const blender::bke::AttributeAccessor b_attributes = b_pointcloud.attributes();
if (b_attributes.domain_size(blender::bke::AttrDomain::Point) == 0) {
return;
}
AttributeSet &attributes = pointcloud->attributes;
static const ustring u_velocity("velocity");
b_attributes.foreach_attribute([&](const blender::bke::AttributeIter &iter) {
const ustring name{std::string_view(iter.name)};
if (need_motion && name == u_velocity) {
const blender::VArraySpan b_attr = *iter.get<blender::float3>();
attr_create_motion_from_velocity(pointcloud, b_attr, motion_scale);
}
if (attributes.find(name)) {
return;
}
const blender::bke::GAttributeReader b_attr = iter.get();
blender::bke::attribute_math::to_static_type(b_attr.varray.type(), [&]<typename BlenderT>() {
using Converter = typename ccl::AttributeConverter<BlenderT>;
using CyclesT = typename Converter::CyclesT;
if constexpr (!std::is_void_v<CyclesT>) {
const blender::VArray<BlenderT> src_varray = b_attr.varray.typed<BlenderT>();
const blender::CommonVArrayInfo info = b_attr.varray.common_info();
if (info.type == blender::CommonVArrayInfo::Type::Single) {
const auto &single_value = *static_cast<const BlenderT *>(info.data);
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_MESH);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());
*data = Converter::convert(single_value);
return;
}
if constexpr (Converter::layout_compatible) {
if (info.type == blender::CommonVArrayInfo::Type::Span && b_attr.sharing_info) {
attributes.add_shared(name,
Converter::type_desc,
ATTR_ELEMENT_VERTEX,
info.data,
src_varray.size(),
b_attr.sharing_info);
return;
}
}
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_VERTEX);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());
const blender::VArraySpan src = src_varray;
for (const int i : src.index_range()) {
data[i] = Converter::convert(src[i]);
}
}
});
});
}
static void export_pointcloud(Scene *scene,
PointCloud *pointcloud,
const blender::PointCloud &b_pointcloud,
const bool need_motion,
const float motion_scale)
{
const blender::Span<blender::float3> b_positions = b_pointcloud.positions();
const blender::bke::AttributeAccessor b_attributes = b_pointcloud.attributes();
pointcloud->resize(b_positions.size());
/* Sync positions, sharing with Blender when possible. */
sync_attribute_from_blender(
pointcloud->attributes,
ATTR_STD_POSITION,
b_attributes.lookup<blender::float3>("position", blender::bke::AttrDomain::Point),
b_positions.size());
pointcloud->tag_position_modified();
/* Sync radius, sharing with Blender when possible, or filling default. */
if (sync_attribute_from_blender(
pointcloud->attributes,
ATTR_STD_RADIUS,
b_attributes.lookup<float>("radius", blender::bke::AttrDomain::Point),
b_positions.size()))
{
pointcloud->tag_radius_modified();
}
else {
float *radius = pointcloud->get_radius_for_write();
std::fill(radius, radius + b_positions.size(), 0.01f);
}
int *shader = pointcloud->get_shader().data();
std::fill(shader, shader + b_positions.size(), 0);
if (pointcloud->need_attribute(scene, ATTR_STD_POINT_RANDOM)) {
Attribute *attr_random = pointcloud->attributes.add(ATTR_STD_POINT_RANDOM);
float *data = attr_random->data_for_write<float>();
for (const int i : b_positions.index_range()) {
data[i] = hash_uint2_to_float(i, 0);
}
}
copy_attributes(pointcloud, b_pointcloud, need_motion, motion_scale);
}
static void export_pointcloud_motion(PointCloud *pointcloud,
const blender::PointCloud &b_pointcloud,
const int motion_step)
{
/* Set motion steps on position and radius attributes. */
Attribute *attr_P = pointcloud->attributes.find(ATTR_STD_POSITION);
Attribute *attr_R = pointcloud->attributes.find(ATTR_STD_RADIUS);
bool new_attribute = false;
if (!attr_P->has_motion()) {
attr_P->add_motion(pointcloud);
attr_R->add_motion(pointcloud);
new_attribute = true;
}
const int num_points = pointcloud->num_points();
const int attr_step = motion_step + 1;
const blender::Span<blender::float3> b_positions = b_pointcloud.positions();
const blender::bke::AttributeAccessor b_attributes = b_pointcloud.attributes();
const bool size_matches = (b_positions.size() == num_points);
bool have_motion = false;
if (size_matches) {
/* Fast path: point count unchanged, sync the whole step from Blender,
* sharing the buffer when possible. */
sync_attribute_motion_step_from_blender(
*attr_P,
attr_step,
b_attributes.lookup<blender::float3>("position", blender::bke::AttrDomain::Point));
if (!sync_attribute_motion_step_from_blender(
*attr_R,
attr_step,
b_attributes.lookup<float>("radius", blender::bke::AttrDomain::Point)))
{
float *mR = attr_R->data_for_write<float>(attr_step);
std::fill(mR, mR + num_points, 0.01f);
}
/* If the buffer is shared from Blender and unchanged across frames, the
* pointer matches the center step's, so the memcmp is skipped. */
const packed_float3 *motion_P = attr_P->data<packed_float3>(attr_step);
const packed_float3 *center_P = pointcloud->get_position();
have_motion = motion_P != center_P &&
std::memcmp(motion_P, center_P, num_points * sizeof(packed_float3)) != 0;
}
else {
/* Slow path: point count differs, copy what overlaps. */
const blender::VArraySpan b_radius = *b_attributes.lookup<float>(
"radius", blender::bke::AttrDomain::Point);
packed_float3 *mP = attr_P->data_for_write<packed_float3>(attr_step);
float *mR = attr_R->data_for_write<float>(attr_step);
for (int i = 0; i < std::min<int>(num_points, b_positions.size()); i++) {
mP[i] = make_float3(b_positions[i][0], b_positions[i][1], b_positions[i][2]);
mR[i] = b_radius.is_empty() ? 0.01f : b_radius[i];
}
}
/* In case of new attribute, verify if there really was any motion. */
if (new_attribute) {
if (!size_matches || !have_motion) {
attr_P->remove_motion();
attr_R->remove_motion();
}
else if (motion_step > 0) {
/* Motion, fill up previous steps that we might have skipped because
* they had no motion, but we need them anyway now. */
for (int step = 0; step < motion_step; step++) {
pointcloud->copy_center_to_motion_step(step);
}
}
}
/* Export attributes */
copy_attributes(pointcloud, b_pointcloud, false, 0.0f);
}
void BlenderSync::sync_pointcloud(PointCloud *pointcloud, BObjectInfo &b_ob_info)
{
const size_t old_numpoints = pointcloud->num_points();
array<Node *> used_shaders = pointcloud->get_used_shaders();
PointCloud new_pointcloud;
new_pointcloud.set_used_shaders(used_shaders);
/* TODO: add option to filter out points in the view layer. */
const blender::PointCloud *b_pointcloud = blender::id_cast<blender::PointCloud *>(
b_ob_info.object_data);
/* Motion blur attribute is relative to seconds, we need it relative to frames. */
const bool need_motion = object_need_motion_attribute(b_ob_info, scene);
const float motion_scale = (need_motion) ? scene->motion_shutter_time() /
(b_scene->r.frs_sec / b_scene->r.frs_sec_base) :
0.0f;
export_pointcloud(scene, &new_pointcloud, *b_pointcloud, need_motion, motion_scale);
if (scene->need_motion() == Scene::MOTION_PASS_INTERACTIVE &&
pointcloud->num_points() == new_pointcloud.num_points())
{
new_pointcloud.set_motion_steps(2);
Attribute *attr_P = pointcloud->attributes.find(ATTR_STD_POSITION);
Attribute *new_attr_P = new_pointcloud.attributes.find(ATTR_STD_POSITION);
if (attr_P->has_motion()) {
new_attr_P->take_motion_from(*attr_P);
}
else {
new_attr_P->add_motion(&new_pointcloud);
new_pointcloud.copy_center_to_motion_step(0);
}
}
/* Update original sockets. */
pointcloud->clear_non_sockets();
for (const SocketType &socket : new_pointcloud.type->inputs) {
/* Those sockets are updated in sync_object, so do not modify them. */
if (socket.name == "use_motion_blur" || socket.name == "used_shaders") {
continue;
}
pointcloud->set_value(socket, new_pointcloud, socket);
}
pointcloud->attributes.update(std::move(new_pointcloud.attributes));
/* Tag update. */
const bool rebuild = (pointcloud && old_numpoints != pointcloud->num_points());
pointcloud->tag_update(scene, rebuild);
}
void BlenderSync::sync_pointcloud_motion(PointCloud *pointcloud,
BObjectInfo &b_ob_info,
const int motion_step)
{
/* Skip if nothing exported. */
if (pointcloud->num_points() == 0) {
return;
}
/* Export deformed coordinates. */
if (ccl::BKE_object_is_deform_modified(b_ob_info, *b_scene, preview)) {
/* PointCloud object. */
const blender::PointCloud *b_pointcloud = blender::id_cast<blender::PointCloud *>(
b_ob_info.object_data);
export_pointcloud_motion(pointcloud, *b_pointcloud, motion_step);
}
else {
/* No deformation on this frame, copy coordinates if other frames did have it. */
pointcloud->copy_center_to_motion_step(motion_step);
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,939 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include <Python.h>
#include "blender/CCL_api.h"
#include "blender/device.h"
#include "blender/session.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "session/denoising.h"
#include "session/merge.h"
#include "util/colorspace.h"
#include "util/debug.h"
#include "util/guiding.h"
#include "util/image_maketx.h"
#include "util/image_metadata.h"
#include "util/log.h"
#include "util/openimagedenoise.h"
#include "util/path.h"
#include "util/string.h"
#include "util/task.h"
#include "util/types.h"
#include "GPU_state.hh"
#include "DNA_screen_types.h"
#include "scene/osl.h"
#ifdef WITH_METAL
# include "device/metal/device.h"
#endif
CCL_NAMESPACE_BEGIN
namespace {
/* Flag describing whether debug flags were synchronized from scene. */
bool debug_flags_set = false;
void *pylong_as_voidptr_typesafe(PyObject *object)
{
if (object == Py_None) {
return nullptr;
}
return PyLong_AsVoidPtr(object);
}
PyObject *pyunicode_from_string(const char *str)
{
/* Ignore errors if device API returns invalid UTF8 strings. */
return PyUnicode_DecodeUTF8(str, strlen(str), "ignore");
}
/* Synchronize debug flags from a given Blender scene.
* Return truth when device list needs invalidation.
*/
void debug_flags_sync_from_scene(blender::Scene &b_scene)
{
DebugFlagsRef flags = DebugFlags();
blender::PointerRNA scene_rna_ptr = RNA_id_pointer_create(&b_scene.id);
blender::PointerRNA cscene = RNA_pointer_get(&scene_rna_ptr, "cycles");
/* Synchronize CPU flags. */
flags.cpu.avx2 = get_boolean(cscene, "debug_use_cpu_avx2");
flags.cpu.sse42 = get_boolean(cscene, "debug_use_cpu_sse42");
flags.cpu.bvh_layout = (BVHLayout)get_enum(cscene, "debug_bvh_layout");
/* Synchronize CUDA flags. */
flags.cuda.adaptive_compile = get_boolean(cscene, "debug_use_cuda_adaptive_compile");
flags.hip.adaptive_compile = get_boolean(cscene, "debug_use_hip_adaptive_compile");
flags.metal.adaptive_compile = get_boolean(cscene, "debug_use_metal_adaptive_compile");
/* Synchronize OptiX flags. */
flags.optix.use_debug = get_boolean(cscene, "debug_use_optix_debug");
/* Synchronize Texture Cache flags. */
flags.texture_cache.use_eviction = get_boolean(cscene, "debug_use_texture_cache_eviction");
flags.texture_cache.preserve_unused = get_int(cscene, "debug_texture_cache_preserve_unused");
}
/* Reset debug flags to default values.
* Return truth when device list needs invalidation.
*/
void debug_flags_reset()
{
DebugFlagsRef flags = DebugFlags();
flags.reset();
}
} /* namespace */
void python_thread_state_save(void **python_thread_state)
{
*python_thread_state = (void *)PyEval_SaveThread();
}
void python_thread_state_restore(void **python_thread_state)
{
PyEval_RestoreThread((PyThreadState *)*python_thread_state);
*python_thread_state = nullptr;
}
static const char *PyC_UnicodeAsBytes(PyObject *py_str, PyObject **coerce)
{
const char *result = PyUnicode_AsUTF8(py_str);
if (result) {
/* 99% of the time this is enough but we better support non unicode
* chars since blender doesn't limit this.
*/
return result;
}
PyErr_Clear();
if (PyBytes_Check(py_str)) {
return PyBytes_AS_STRING(py_str);
}
*coerce = PyUnicode_EncodeFSDefault(py_str);
if (*coerce) {
return PyBytes_AS_STRING(*coerce);
}
/* Clear the error, so Cycles can be at least used without
* GPU and OSL support,
*/
PyErr_Clear();
return "";
}
static PyObject *init_func(PyObject * /*self*/, PyObject *args)
{
PyObject *path;
PyObject *user_path;
int headless;
if (!PyArg_ParseTuple(args, "OOi", &path, &user_path, &headless)) {
return nullptr;
}
PyObject *path_coerce = nullptr;
PyObject *user_path_coerce = nullptr;
path_init(PyC_UnicodeAsBytes(path, &path_coerce),
PyC_UnicodeAsBytes(user_path, &user_path_coerce));
Py_XDECREF(path_coerce);
Py_XDECREF(user_path_coerce);
BlenderSession::headless = headless;
Py_RETURN_NONE;
}
static PyObject *exit_func(PyObject * /*self*/, PyObject * /*args*/)
{
#ifdef WITH_METAL
device_metal_exit();
#endif
ColorSpaceManager::free_memory();
OSLManager::free_memory();
TaskScheduler::free_memory();
Device::free_memory();
Py_RETURN_NONE;
}
static PyObject *create_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
PyObject *pypreferences;
PyObject *pydata;
PyObject *pyscreen;
PyObject *pyregion;
PyObject *pyv3d;
PyObject *pyrv3d;
int preview_osl;
if (!PyArg_ParseTuple(args,
"OOOOOOOi",
&pyengine,
&pypreferences,
&pydata,
&pyscreen,
&pyregion,
&pyv3d,
&pyrv3d,
&preview_osl))
{
return nullptr;
}
/* RNA */
blender::ID *bScreen = (blender::ID *)PyLong_AsVoidPtr(pyscreen);
blender::RenderEngine *engine = static_cast<blender::RenderEngine *>(PyLong_AsVoidPtr(pyengine));
blender::UserDef *preferences = static_cast<blender::UserDef *>(PyLong_AsVoidPtr(pypreferences));
blender::Main *data = static_cast<blender::Main *>(PyLong_AsVoidPtr(pydata));
blender::View3D *v3d = static_cast<blender::View3D *>(pylong_as_voidptr_typesafe(pyv3d));
blender::ARegion *region = static_cast<blender::ARegion *>(pylong_as_voidptr_typesafe(pyregion));
/* create session */
BlenderSession *session;
if (region) {
blender::RegionView3D *rv3d = static_cast<blender::RegionView3D *>(region->regiondata);
/* interactive viewport session */
const int width = region->winx;
const int height = region->winy;
session = new BlenderSession(*engine,
*preferences,
*data,
blender::id_cast<blender::bScreen *>(bScreen),
v3d,
rv3d,
width,
height);
}
else {
/* offline session or preview render */
session = new BlenderSession(*engine, *preferences, *data, preview_osl);
}
return PyLong_FromVoidPtr(session);
}
static PyObject *free_func(PyObject * /*self*/, PyObject *value)
{
delete (BlenderSession *)PyLong_AsVoidPtr(value);
Py_RETURN_NONE;
}
static PyObject *render_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pysession;
PyObject *pydepsgraph;
if (!PyArg_ParseTuple(args, "OO", &pysession, &pydepsgraph)) {
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
blender::Depsgraph *b_depsgraph = static_cast<blender::Depsgraph *>(
PyLong_AsVoidPtr(pydepsgraph));
/* Allow Blender to execute other Python scripts. */
python_thread_state_save(&session->python_thread_state);
session->render(*b_depsgraph);
python_thread_state_restore(&session->python_thread_state);
Py_RETURN_NONE;
}
static PyObject *render_frame_finish_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pysession;
if (!PyArg_ParseTuple(args, "O", &pysession)) {
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
/* Allow Blender to execute other Python scripts. */
python_thread_state_save(&session->python_thread_state);
session->render_frame_finish();
python_thread_state_restore(&session->python_thread_state);
Py_RETURN_NONE;
}
static PyObject *draw_func(PyObject * /*self*/, PyObject *args)
{
PyObject *py_session;
PyObject *py_graph;
PyObject *py_screen;
PyObject *py_space_image;
if (!PyArg_ParseTuple(args, "OOOO", &py_session, &py_graph, &py_screen, &py_space_image)) {
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(py_session);
blender::ID *b_screen = (blender::ID *)PyLong_AsVoidPtr(py_screen);
blender::SpaceImage *b_space_image = static_cast<blender::SpaceImage *>(
pylong_as_voidptr_typesafe(py_space_image));
session->draw(blender::id_cast<blender::bScreen &>(*b_screen), *b_space_image);
Py_RETURN_NONE;
}
/* pixel_array and result passed as pointers */
static PyObject *bake_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pysession;
PyObject *pydepsgraph;
PyObject *pyobject;
const char *pass_type;
int pass_filter;
int width;
int height;
if (!PyArg_ParseTuple(args,
"OOOsiii",
&pysession,
&pydepsgraph,
&pyobject,
&pass_type,
&pass_filter,
&width,
&height))
{
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
blender::Depsgraph *b_depsgraph = static_cast<blender::Depsgraph *>(
PyLong_AsVoidPtr(pydepsgraph));
blender::Object *b_object = static_cast<blender::Object *>(PyLong_AsVoidPtr(pyobject));
python_thread_state_save(&session->python_thread_state);
session->bake(*b_depsgraph, *b_object, pass_type, pass_filter, width, height);
python_thread_state_restore(&session->python_thread_state);
Py_RETURN_NONE;
}
static PyObject *view_draw_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pysession;
PyObject *pygraph;
PyObject *pyv3d;
PyObject *pyrv3d;
if (!PyArg_ParseTuple(args, "OOOO", &pysession, &pygraph, &pyv3d, &pyrv3d)) {
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
if (PyLong_AsVoidPtr(pyrv3d)) {
/* 3d view drawing */
int viewport[4];
blender::GPU_viewport_size_get_i(viewport);
session->view_draw(viewport[2], viewport[3]);
}
Py_RETURN_NONE;
}
static PyObject *reset_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pysession;
PyObject *pydata;
PyObject *pydepsgraph;
if (!PyArg_ParseTuple(args, "OOO", &pysession, &pydata, &pydepsgraph)) {
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
blender::Main *b_data = static_cast<blender::Main *>(PyLong_AsVoidPtr(pydata));
blender::Depsgraph *b_depsgraph = static_cast<blender::Depsgraph *>(
PyLong_AsVoidPtr(pydepsgraph));
python_thread_state_save(&session->python_thread_state);
session->reset_session(*b_data, *b_depsgraph);
python_thread_state_restore(&session->python_thread_state);
Py_RETURN_NONE;
}
static PyObject *sync_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pysession;
PyObject *pydepsgraph;
if (!PyArg_ParseTuple(args, "OO", &pysession, &pydepsgraph)) {
return nullptr;
}
BlenderSession *session = (BlenderSession *)PyLong_AsVoidPtr(pysession);
blender::Depsgraph *b_depsgraph = static_cast<blender::Depsgraph *>(
PyLong_AsVoidPtr(pydepsgraph));
python_thread_state_save(&session->python_thread_state);
session->synchronize(*b_depsgraph);
python_thread_state_restore(&session->python_thread_state);
Py_RETURN_NONE;
}
static PyObject *available_devices_func(PyObject * /*self*/, PyObject *args)
{
const char *type_name;
if (!PyArg_ParseTuple(args, "s", &type_name)) {
return nullptr;
}
const DeviceType type = Device::type_from_string(type_name);
/* "NONE" is defined by the add-on, see: `CyclesPreferences.get_device_types`. */
if ((type == DEVICE_NONE) && (strcmp(type_name, "NONE") != 0)) {
PyErr_Format(PyExc_ValueError, "Device \"%s\" not known.", type_name);
return nullptr;
}
uint mask = (type == DEVICE_NONE) ? DEVICE_MASK_ALL : DEVICE_MASK(type);
mask |= DEVICE_MASK_CPU;
vector<DeviceInfo> devices = Device::available_devices(mask);
PyObject *ret = PyTuple_New(devices.size());
for (size_t i = 0; i < devices.size(); i++) {
const DeviceInfo &device = devices[i];
const string type_name = Device::string_from_type(device.type);
PyObject *device_tuple = PyTuple_New(8);
PyTuple_SET_ITEM(device_tuple, 0, pyunicode_from_string(device.description.c_str()));
PyTuple_SET_ITEM(device_tuple, 1, pyunicode_from_string(type_name.c_str()));
PyTuple_SET_ITEM(device_tuple, 2, pyunicode_from_string(device.id.c_str()));
PyTuple_SET_ITEM(device_tuple, 3, PyBool_FromLong(device.has_peer_memory));
PyTuple_SET_ITEM(device_tuple, 4, PyBool_FromLong(device.use_hardware_raytracing));
PyTuple_SET_ITEM(
device_tuple, 5, PyBool_FromLong(device.denoisers & DENOISER_OPENIMAGEDENOISE));
PyTuple_SET_ITEM(device_tuple, 6, PyBool_FromLong(device.denoisers & DENOISER_OPTIX));
PyTuple_SET_ITEM(device_tuple, 7, PyBool_FromLong(device.has_execution_optimization));
PyTuple_SET_ITEM(ret, i, device_tuple);
}
return ret;
}
#ifdef WITH_OSL
static PyObject *osl_compile_func(PyObject * /*self*/, PyObject *args)
{
const char *inputfile = nullptr;
const char *outputfile = nullptr;
if (!PyArg_ParseTuple(args, "ss", &inputfile, &outputfile)) {
return nullptr;
}
/* return */
if (!OSLManager::osl_compile(inputfile, outputfile)) {
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
#endif
static PyObject *system_info_func(PyObject * /*self*/, PyObject * /*value*/)
{
const string system_info = Device::device_capabilities();
return pyunicode_from_string(system_info.c_str());
}
static bool image_parse_filepaths(PyObject *pyfilepaths, vector<string> &filepaths)
{
if (PyUnicode_Check(pyfilepaths)) {
const char *filepath = PyUnicode_AsUTF8(pyfilepaths);
filepaths.push_back(filepath);
return true;
}
PyObject *sequence = PySequence_Fast(pyfilepaths,
"File paths must be a string or sequence of strings");
if (sequence == nullptr) {
return false;
}
for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(sequence); i++) {
PyObject *item = PySequence_Fast_GET_ITEM(sequence, i);
const char *filepath = PyUnicode_AsUTF8(item);
if (filepath == nullptr) {
PyErr_SetString(PyExc_ValueError, "File paths must be a string or sequence of strings.");
Py_DECREF(sequence);
return false;
}
filepaths.push_back(filepath);
}
Py_DECREF(sequence);
return true;
}
static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
{
static const char *keyword_list[] = {
"preferences", "scene", "view_layer", "input", "output", nullptr};
PyObject *pypreferences;
PyObject *pyscene;
PyObject *pyviewlayer;
PyObject *pyinput;
PyObject *pyoutput = nullptr;
if (!PyArg_ParseTupleAndKeywords(args,
keywords,
"OOOO|O",
(char **)keyword_list,
&pypreferences,
&pyscene,
&pyviewlayer,
&pyinput,
&pyoutput))
{
return nullptr;
}
/* Get device specification from preferences and scene. */
blender::UserDef *b_preferences = static_cast<blender::UserDef *>(
PyLong_AsVoidPtr(pypreferences));
blender::Scene *b_scene = static_cast<blender::Scene *>(PyLong_AsVoidPtr(pyscene));
DeviceInfo preferences_device;
const DeviceInfo pathtrace_device = blender_device_info(
*b_preferences, *b_scene, true, true, preferences_device);
/* Get denoising parameters from view layer. */
blender::ViewLayer *b_view_layer = static_cast<blender::ViewLayer *>(
PyLong_AsVoidPtr(pyviewlayer));
DenoiseParams params = BlenderSync::get_denoise_params(
*b_scene, b_view_layer, true, preferences_device);
params.use = true;
/* Parse file paths list. */
vector<string> input;
vector<string> output;
if (!image_parse_filepaths(pyinput, input)) {
return nullptr;
}
if (pyoutput) {
if (!image_parse_filepaths(pyoutput, output)) {
return nullptr;
}
}
else {
output = input;
}
if (input.empty()) {
PyErr_SetString(PyExc_ValueError, "No input file paths specified.");
return nullptr;
}
if (input.size() != output.size()) {
PyErr_SetString(PyExc_ValueError, "Number of input and output file paths does not match.");
return nullptr;
}
/* Create denoiser. */
/* We are using preference device here, because path trace device will be identical to it unless
* scene is setting CPU render or command line override render device. But both of this options
* are for render, not for denoising. */
DenoiserPipeline denoiser(preferences_device, params);
denoiser.input = input;
denoiser.output = output;
/* Run denoiser. */
if (!denoiser.run()) {
PyErr_SetString(PyExc_ValueError, denoiser.error.c_str());
return nullptr;
}
Py_RETURN_NONE;
}
static PyObject *merge_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
{
static const char *keyword_list[] = {"input", "output", nullptr};
PyObject *pyinput;
PyObject *pyoutput = nullptr;
if (!PyArg_ParseTupleAndKeywords(
args, keywords, "OO", (char **)keyword_list, &pyinput, &pyoutput))
{
return nullptr;
}
/* Parse input list. */
vector<string> input;
if (!image_parse_filepaths(pyinput, input)) {
return nullptr;
}
/* Parse output string. */
if (!PyUnicode_Check(pyoutput)) {
PyErr_SetString(PyExc_ValueError, "Output must be a string.");
return nullptr;
}
const string output = PyUnicode_AsUTF8(pyoutput);
/* Merge. */
ImageMerger merger;
merger.input = input;
merger.output = output;
if (!merger.run()) {
PyErr_SetString(PyExc_ValueError, merger.error.c_str());
return nullptr;
}
Py_RETURN_NONE;
}
static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyscene;
if (!PyArg_ParseTuple(args, "O", &pyscene)) {
return nullptr;
}
blender::Scene *b_scene = static_cast<blender::Scene *>(PyLong_AsVoidPtr(pyscene));
debug_flags_sync_from_scene(*b_scene);
debug_flags_set = true;
Py_RETURN_NONE;
}
static PyObject *debug_flags_reset_func(PyObject * /*self*/, PyObject * /*args*/)
{
debug_flags_reset();
if (debug_flags_set) {
debug_flags_set = false;
}
Py_RETURN_NONE;
}
static PyObject *enable_print_stats_func(PyObject * /*self*/, PyObject * /*args*/)
{
BlenderSession::print_render_stats = true;
Py_RETURN_NONE;
}
static PyObject *get_device_types_func(PyObject * /*self*/, PyObject * /*args*/)
{
const vector<DeviceType> device_types = Device::available_types();
bool has_cuda = false;
bool has_optix = false;
bool has_hip = false;
bool has_metal = false;
bool has_oneapi = false;
bool has_hiprt = false;
for (const DeviceType device_type : device_types) {
has_cuda |= (device_type == DEVICE_CUDA);
has_optix |= (device_type == DEVICE_OPTIX);
has_hip |= (device_type == DEVICE_HIP);
has_metal |= (device_type == DEVICE_METAL);
has_oneapi |= (device_type == DEVICE_ONEAPI);
has_hiprt |= (device_type == DEVICE_HIPRT);
}
PyObject *list = PyTuple_New(6);
PyTuple_SET_ITEM(list, 0, PyBool_FromLong(has_cuda));
PyTuple_SET_ITEM(list, 1, PyBool_FromLong(has_optix));
PyTuple_SET_ITEM(list, 2, PyBool_FromLong(has_hip));
PyTuple_SET_ITEM(list, 3, PyBool_FromLong(has_metal));
PyTuple_SET_ITEM(list, 4, PyBool_FromLong(has_oneapi));
PyTuple_SET_ITEM(list, 5, PyBool_FromLong(has_hiprt));
return list;
}
static PyObject *set_device_override_func(PyObject * /*self*/, PyObject *arg)
{
PyObject *override_string = PyObject_Str(arg);
string override = PyUnicode_AsUTF8(override_string);
Py_DECREF(override_string);
bool include_cpu = false;
const string cpu_suffix = "+CPU";
if (string_endswith(override, cpu_suffix)) {
include_cpu = true;
override = override.substr(0, override.length() - cpu_suffix.length());
}
if (override == "CPU") {
BlenderSession::device_override = DEVICE_MASK_CPU;
}
else if (override == "CUDA") {
BlenderSession::device_override = DEVICE_MASK_CUDA;
}
else if (override == "OPTIX") {
BlenderSession::device_override = DEVICE_MASK_OPTIX;
}
else if (override == "HIP") {
BlenderSession::device_override = DEVICE_MASK_HIP;
}
else if (override == "METAL") {
BlenderSession::device_override = DEVICE_MASK_METAL;
}
else if (override == "ONEAPI") {
BlenderSession::device_override = DEVICE_MASK_ONEAPI;
}
else {
LOG_ERROR << override << " is not a valid Cycles device.";
Py_RETURN_FALSE;
}
if (include_cpu) {
BlenderSession::device_override = (DeviceTypeMask)(BlenderSession::device_override |
DEVICE_MASK_CPU);
}
Py_RETURN_TRUE;
}
static PyObject *maketx_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
{
static const char *keyword_list[] = {
"filepath", "colorspace", "alpha_type", "cache_dir", nullptr};
const char *filepath = nullptr;
const char *colorspace = "auto";
const char *alpha_type_str = "auto";
const char *cache_dir = "";
if (!PyArg_ParseTupleAndKeywords(args,
keywords,
"s|sss",
(char **)keyword_list,
&filepath,
&colorspace,
&alpha_type_str,
&cache_dir))
{
return nullptr;
}
/* Alpha type. */
ImageAlphaType alpha_type;
if (strcmp(alpha_type_str, "straight") == 0) {
alpha_type = IMAGE_ALPHA_UNASSOCIATED;
}
else if (strcmp(alpha_type_str, "premultiplied") == 0) {
alpha_type = IMAGE_ALPHA_ASSOCIATED;
}
else if (strcmp(alpha_type_str, "channel_packed") == 0) {
alpha_type = IMAGE_ALPHA_CHANNEL_PACKED;
}
else if (strcmp(alpha_type_str, "none") == 0) {
alpha_type = IMAGE_ALPHA_IGNORE;
}
else if (strcmp(alpha_type_str, "auto") == 0) {
alpha_type = IMAGE_ALPHA_AUTO;
}
else {
PyErr_Format(PyExc_ValueError, "Unknown alpha type: %s", alpha_type_str);
return nullptr;
}
/* Colorspace. */
const ustring colorspace_ustring = (strcmp(colorspace, "auto") == 0) ? u_colorspace_auto :
ustring(colorspace);
/* Resolve output path, and check if tx file is already up to date. */
string out_filepath;
ccl::ImageMetaData out_metadata;
const bool up_to_date = resolve_tx(filepath,
cache_dir,
colorspace_ustring,
alpha_type,
IMAGE_FORMAT_PLAIN,
out_filepath,
out_metadata);
if (out_filepath.empty()) {
LOG_ERROR << "Source image not found: " << filepath;
PyErr_Format(PyExc_RuntimeError, "Source image not found");
return nullptr;
}
/* Generate tx file if needed. */
if (!up_to_date) {
bool ok;
Py_BEGIN_ALLOW_THREADS;
ok = make_tx(filepath, out_filepath, colorspace_ustring, alpha_type, IMAGE_FORMAT_PLAIN);
Py_END_ALLOW_THREADS;
if (!ok) {
LOG_ERROR << "Failed to generate tx file";
PyErr_Format(PyExc_RuntimeError, "Failed to generate tx file");
return nullptr;
}
}
return pyunicode_from_string(out_filepath.c_str());
}
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-function-type"
# else
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-function-type"
# endif
#endif
static PyMethodDef methods[] = {
{"init", init_func, METH_VARARGS, ""},
{"exit", exit_func, METH_VARARGS, ""},
{"create", create_func, METH_VARARGS, ""},
{"free", free_func, METH_O, ""},
{"render", render_func, METH_VARARGS, ""},
{"render_frame_finish", render_frame_finish_func, METH_VARARGS, ""},
{"draw", draw_func, METH_VARARGS, ""},
{"bake", bake_func, METH_VARARGS, ""},
{"view_draw", view_draw_func, METH_VARARGS, ""},
{"sync", sync_func, METH_VARARGS, ""},
{"reset", reset_func, METH_VARARGS, ""},
#ifdef WITH_OSL
{"osl_compile", osl_compile_func, METH_VARARGS, ""},
#endif
{"available_devices", available_devices_func, METH_VARARGS, ""},
{"system_info", system_info_func, METH_NOARGS, ""},
/* Standalone denoising */
{"denoise", (PyCFunction)denoise_func, METH_VARARGS | METH_KEYWORDS, ""},
{"merge", (PyCFunction)merge_func, METH_VARARGS | METH_KEYWORDS, ""},
/* Debugging routines */
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},
/* Statistics. */
{"enable_print_stats", enable_print_stats_func, METH_NOARGS, ""},
/* Compute Device selection */
{"get_device_types", get_device_types_func, METH_VARARGS, ""},
{"set_device_override", set_device_override_func, METH_O, ""},
/* Texture cache */
{"maketx", (PyCFunction)maketx_func, METH_VARARGS | METH_KEYWORDS, ""},
{nullptr, nullptr, 0, nullptr},
};
#ifdef __GNUC__
# ifdef __clang__
# pragma clang diagnostic pop
# else
# pragma GCC diagnostic pop
# endif
#endif
static struct PyModuleDef module = {
/*m_base*/ PyModuleDef_HEAD_INIT,
/*m_name*/ "_cycles",
/*m_doc*/ "Blender cycles render integration",
/*m_size*/ -1,
/*m_methods*/ methods,
/*m_slots*/ nullptr,
/*m_traverse*/ nullptr,
/*m_clear*/ nullptr,
/*m_free*/ nullptr,
};
CCL_NAMESPACE_END
void *blender::CCL_python_module_init()
{
PyObject *mod = PyModule_Create(&ccl::module);
#ifdef WITH_OSL
/* TODO(sergey): This gives us library we've been linking against.
* In theory with dynamic OSL library it might not be
* accurate, but there's nothing in OSL API which we
* might use to get version in runtime.
*/
const int curversion = OSL_LIBRARY_VERSION_CODE;
PyModule_AddObjectRef(mod, "with_osl", Py_True);
PyModule_AddObject(
mod,
"osl_version",
Py_BuildValue("(iii)", curversion / 10000, (curversion / 100) % 100, curversion % 100));
PyModule_AddObject(
mod,
"osl_version_string",
PyUnicode_FromFormat(
"%2d, %2d, %2d", curversion / 10000, (curversion / 100) % 100, curversion % 100));
#else
PyModule_AddObjectRef(mod, "with_osl", Py_False);
PyModule_AddStringConstant(mod, "osl_version", "unknown");
PyModule_AddStringConstant(mod, "osl_version_string", "unknown");
#endif
if (ccl::guiding_supported()) {
PyModule_AddObjectRef(mod, "with_path_guiding", Py_True);
}
else {
PyModule_AddObjectRef(mod, "with_path_guiding", Py_False);
}
#ifdef WITH_EMBREE
PyModule_AddObjectRef(mod, "with_embree", Py_True);
#else /* WITH_EMBREE */
PyModule_AddObjectRef(mod, "with_embree", Py_False);
#endif /* WITH_EMBREE */
#ifdef WITH_EMBREE_GPU
PyModule_AddObjectRef(mod, "with_embree_gpu", Py_True);
#else /* WITH_EMBREE_GPU */
PyModule_AddObjectRef(mod, "with_embree_gpu", Py_False);
#endif /* WITH_EMBREE_GPU */
if (ccl::openimagedenoise_supported()) {
PyModule_AddObjectRef(mod, "with_openimagedenoise", Py_True);
}
else {
PyModule_AddObjectRef(mod, "with_openimagedenoise", Py_False);
}
#ifdef WITH_CYCLES_DEBUG
PyModule_AddObjectRef(mod, "with_debug", Py_True);
#else /* WITH_CYCLES_DEBUG */
PyModule_AddObjectRef(mod, "with_debug", Py_False);
#endif /* WITH_CYCLES_DEBUG */
return (void *)mod;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,166 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "device/device.h"
#include "scene/scene.h"
#include "session/session.h"
#include "util/unique_ptr.h"
#include "util/vector.h"
namespace blender {
struct bScreen;
struct Depsgraph;
struct Main;
struct Object;
struct RegionView3D;
struct RenderData;
struct RenderEngine;
struct Scene;
struct SpaceImage;
struct UserDef;
struct View3D;
} // namespace blender
CCL_NAMESPACE_BEGIN
class BlenderDisplayDriver;
class BlenderSync;
class ImageMetaData;
class Scene;
class Session;
class BlenderSession {
public:
BlenderSession(blender::RenderEngine &b_engine,
blender::UserDef &b_userpref,
blender::Main &b_data,
bool preview_osl);
BlenderSession(blender::RenderEngine &b_engine,
blender::UserDef &b_userpref,
blender::Main &b_data,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d,
const int width,
int height);
~BlenderSession();
/* session */
void create_session();
void free_session();
void reset_session(blender::Main &b_data, blender::Depsgraph &b_depsgraph);
/* offline render */
void render(blender::Depsgraph &b_depsgraph);
void render_frame_finish();
void bake(blender::Depsgraph &b_depsgraph_,
blender::Object &b_object,
const string &bake_type,
const int bake_filter,
const int bake_width,
const int bake_height);
void full_buffer_written(string_view filename);
/* interactive updates */
void synchronize(blender::Depsgraph &b_depsgraph);
/* drawing */
void draw(blender::bScreen &b_screen, blender::SpaceImage &space_image);
void view_draw(const int w, const int h);
void tag_redraw();
void tag_update();
void get_status(string &status, string &substatus);
void get_progress(double &progress, double &total_time, double &render_time);
void test_cancel();
void update_status_progress();
void update_bake_progress();
bool background;
unique_ptr<Session> session;
Scene *scene;
unique_ptr<BlenderSync> sync;
double last_redraw_time;
blender::RenderEngine &b_engine;
blender::UserDef &b_userpref;
blender::Main *b_data;
blender::RenderData *b_render;
blender::Depsgraph *b_depsgraph;
/* NOTE: Blender's scene might become invalid after call
* #free_blender_memory_if_possible(). */
blender::Scene *b_scene;
blender::bScreen *b_screen;
blender::View3D *b_v3d;
blender::RegionView3D *b_rv3d;
string b_rlay_name;
string b_rview_name;
string last_status;
string last_error;
double last_progress;
double last_status_time;
int width, height;
float pixelsize;
bool preview_osl;
double start_resize_time;
void *python_thread_state;
bool use_developer_ui;
/* Global state which is common for all render sessions created from Blender.
* Usually denotes command line arguments.
*/
static DeviceTypeMask device_override;
/* Blender is running from the command line, no windows are shown and some
* extra render optimization is possible (possible to free draw-only data and
* so on.
*/
static bool headless;
static bool print_render_stats;
protected:
void stamp_view_layer_metadata(Scene *scene, const string &view_layer_name);
/* Check whether session error happened.
* If so, it is reported to the render engine and true is returned.
* Otherwise false is returned. */
bool check_and_report_session_error();
void builtin_images_load();
/* Is used after each render layer synchronization is done with the goal
* of freeing render engine data which is held from Blender side (for
* example, dependency graph).
*/
void free_blender_memory_if_possible();
void ensure_display_driver_if_needed();
struct {
thread_mutex mutex;
int last_pass_index = -1;
} draw_state_;
/* NOTE: The BlenderSession references the display driver. */
BlenderDisplayDriver *display_driver_ = nullptr;
vector<string> full_buffer_files_;
int bake_id = 0;
};
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,308 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "DNA_userdef_types.h"
#include "RNA_types.hh"
#include "blender/id_map.h"
#include "blender/util.h"
#include "blender/viewport.h"
#include "scene/scene.h"
#include "session/session.h"
#include "util/map.h"
#include "util/set.h"
namespace blender {
struct DEGObjectIterData;
struct MeshSequenceCacheModifier;
} // namespace blender
CCL_NAMESPACE_BEGIN
class Background;
class BlenderObjectCulling;
class BlenderViewportParameters;
class Camera;
class Film;
class Hair;
class Light;
class Mesh;
class Object;
class ParticleSystem;
class Scene;
class Shader;
class ShaderGraph;
class TaskPool;
class BlenderSync {
public:
BlenderSync(blender::RenderEngine &b_engine,
blender::Main &b_data,
blender::Scene &b_scene,
Scene *scene,
bool preview,
bool use_developer_ui,
Progress &progress);
~BlenderSync();
void reset(blender::Main &b_data, blender::Scene &b_scene);
void tag_update();
void set_bake_target(blender::Object &b_object);
/* sync */
void sync_recalc(blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d);
void sync_data(blender::RenderData &b_render,
blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d,
const int width,
const int height,
void **python_thread_state,
const DeviceInfo &denoise_device_info);
void sync_view_layer(blender::ViewLayer &b_view_layer);
void sync_render_passes(blender::RenderLayer &b_rlay, blender::ViewLayer &b_view_layer);
void sync_integrator(blender::ViewLayer &b_view_layer,
bool background,
const DeviceInfo &denoise_device_info);
void sync_scene_attributes();
void sync_camera(const blender::RenderData &b_render,
const int width,
const int height,
const char *viewname);
void sync_view(blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d,
const int width,
const int height);
int get_layer_samples()
{
return view_layer.samples;
}
int get_layer_bound_samples()
{
return view_layer.bound_samples;
}
/* Early data free. */
void free_data_after_sync(blender::Depsgraph &b_depsgraph);
/* get parameters */
static SceneParams get_scene_params(blender::UserDef &b_preferences,
blender::Main &b_data,
blender::Scene &b_scene,
const bool background,
const bool use_developer_ui);
static SessionParams get_session_params(blender::RenderEngine &b_engine,
blender::UserDef &b_preferences,
blender::Scene &b_scene,
bool background,
float pixelsize);
static bool get_session_pause(blender::Scene &b_scene, bool background);
static BufferParams get_buffer_params(blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d,
Camera *cam,
const int width,
const int height);
static DenoiseParams get_denoise_params(blender::Scene &b_scene,
blender::ViewLayer *b_view_layer,
bool background,
const DeviceInfo &denoise_device);
private:
/* sync */
void sync_lights(blender::Depsgraph &b_depsgraph, bool update_all, bool update_time);
void sync_materials(blender::Depsgraph &b_depsgraph, bool update_all, bool update_time);
void sync_objects(blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
const float motion_time = 0.0f);
void sync_objects_and_motion(blender::RenderData &b_render,
blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
blender::RegionView3D *b_rv3d,
const int width,
const int height,
void **python_thread_state);
void sync_film(blender::ViewLayer &b_view_layer,
blender::bScreen *b_screen,
blender::View3D *b_v3d);
void sync_view();
/* Shader */
array<Node *> find_used_shaders(blender::Object &b_ob);
void sync_world(blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
bool update_all,
bool update_time);
void sync_shaders(blender::Depsgraph &b_depsgraph,
blender::bScreen *b_screen,
blender::View3D *b_v3d,
bool update_all,
bool update_time);
void sync_nodes(Shader *shader, blender::bNodeTree &b_ntree);
bool scene_attr_needs_recalc(Shader *shader, blender::Depsgraph &b_depsgraph);
void resolve_view_layer_attributes(Shader *shader,
ShaderGraph *graph,
blender::Depsgraph &b_depsgraph);
/* Object */
Object *sync_object(blender::ViewLayer &b_view_layer,
blender::Object &b_ob,
blender::DEGObjectIterData &b_deg_iter_data,
const float motion_time,
bool use_particle_hair,
bool show_lights,
BlenderObjectCulling &culling,
TaskPool *geom_task_pool);
void sync_object_motion_init(blender::Object &b_parent, blender::Object &b_ob, Object *object);
void sync_procedural(blender::Object &b_ob,
blender::MeshSequenceCacheModifier &b_mesh_cache,
bool has_subdivision);
bool sync_object_attributes(blender::Object &b_ob,
blender::DEGObjectIterData &b_deg_iter_data,
Object *object);
/* Volume */
void sync_volume(BObjectInfo &b_ob_info, Volume *volume);
/* Mesh */
void sync_mesh(BObjectInfo &b_ob_info, Mesh *mesh);
void sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, int motion_step);
/* Hair */
void sync_hair(BObjectInfo &b_ob_info, Hair *hair);
void sync_hair_motion(BObjectInfo &b_ob_info, Hair *hair, int motion_step);
void sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, const int motion_step = 0);
void sync_particle_hair(Hair *hair,
const blender::Mesh &b_mesh,
BObjectInfo &b_ob_info,
bool motion,
const int motion_step = 0);
bool object_has_particle_hair(blender::Object *b_ob);
/* Point Cloud */
void sync_pointcloud(PointCloud *pointcloud, BObjectInfo &b_ob_info);
void sync_pointcloud_motion(PointCloud *pointcloud,
BObjectInfo &b_ob_info,
const int motion_step = 0);
/* Camera */
void sync_camera_motion(const blender::RenderData &b_render,
blender::Object *b_ob,
const int width,
const int height,
const float motion_time);
/* Geometry */
Geometry *sync_geometry(BObjectInfo &b_ob_info,
bool object_updated,
bool use_particle_hair,
TaskPool *task_pool);
void sync_geometry_motion(BObjectInfo &b_ob_info,
Object *object,
const float motion_time,
bool use_particle_hair,
TaskPool *task_pool);
/* Light */
Geometry *create_light(BObjectInfo &b_ob_info);
void sync_light(BObjectInfo &b_ob_info, Light *light);
void sync_background_light(blender::bScreen *b_screen, blender::View3D *b_v3d);
/* Particles */
bool sync_dupli_particle(blender::Object &b_parent,
blender::DEGObjectIterData &b_deg_iter_data,
blender::Object &b_ob,
Object *object);
/* Images. */
void sync_images();
/* util */
void find_shader(const blender::ID *id, array<Node *> &used_shaders, Shader *default_shader);
bool BKE_object_is_modified(blender::Object &b_ob);
bool object_is_geometry(BObjectInfo &b_ob_info);
bool object_can_have_geometry(blender::Object &b_ob);
bool object_is_light(blender::Object &b_ob);
bool object_is_camera(blender::Object &b_ob);
blender::Object *get_camera_object(blender::View3D *b_v3d, blender::RegionView3D *b_rv3d);
blender::Object *get_dicing_camera_object(blender::View3D *b_v3d, blender::RegionView3D *b_rv3d);
/* variables */
blender::RenderEngine *b_engine;
blender::Main *b_data;
blender::Scene *b_scene;
blender::Object *b_bake_target;
enum ShaderFlags { SHADER_WITH_LAYER_ATTRS };
id_map<const void *, Shader, ShaderFlags> shader_map;
/* To keep track of the AOVs in consecutive view layers that are rendered, this is the old data
* for comparing. */
blender::Vector<std::pair<std::string, int>> shader_view_layer_aovs;
id_map<ObjectKey, Object> object_map;
id_map<void *, Procedural> procedural_map;
id_map<GeometryKey, Geometry> geometry_map;
id_map<ParticleSystemKey, ParticleSystem> particle_system_map;
set<Geometry *> geometry_synced;
set<Geometry *> geometry_motion_synced;
set<Geometry *> geometry_motion_attribute_synced;
/** Remember which geometries come from which objects to be able to sync them after changes. */
map<void *, set<blender::ID *>> instance_geometries_by_object;
set<float> motion_times;
void *world_map;
bool world_recalc;
BlenderViewportParameters viewport_parameters;
Scene *scene;
bool preview;
bool use_adaptive_subdivision = false;
bool use_developer_ui;
CurveShapeType curve_shape = CURVE_RIBBON;
float dicing_rate;
int max_subdivisions;
struct RenderLayerInfo {
string name;
blender::Material *material_override = nullptr;
blender::World *world_override = nullptr;
bool use_background_shader = true;
bool use_surfaces = true;
bool use_hair = true;
bool use_volumes = true;
bool use_motion_blur = true;
int samples = 0;
bool bound_samples = false;
} view_layer;
Progress &progress;
/* Indicates that `sync_recalc()` detected changes in the scene.
* If this flag is false then the data is considered to be up-to-date and will not be
* synchronized at all. */
bool has_updates_ = true;
float frame_last_synced = 0;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "DNA_image_types.h"
#include "blender/CCL_api.h"
#include "util/image_maketx.h"
#include "util/image_metadata.h"
#include "util/types_image.h"
namespace blender {
bool CCL_resolve_texture_cache(const Image *image,
const char *filepath,
const char *texture_cache_directory,
std::string &r_tx_filepath)
{
ccl::ImageMetaData tx_metadata;
return ccl::resolve_tx(filepath,
texture_cache_directory,
ccl::ustring(image->colorspace_settings.name),
ccl::ImageAlphaType(image->alpha_mode),
ccl::IMAGE_FORMAT_PLAIN,
r_tx_filepath,
tx_metadata);
}
bool CCL_generate_texture_cache(const Image *image,
const char *filepath,
const char *texture_cache_directory)
{
std::string tx_filepath;
ccl::ImageMetaData tx_metadata;
const bool is_valid = ccl::resolve_tx(filepath,
texture_cache_directory,
ccl::ustring(image->colorspace_settings.name),
ccl::ImageAlphaType(image->alpha_mode),
ccl::IMAGE_FORMAT_PLAIN,
tx_filepath,
tx_metadata);
if (is_valid) {
return true;
}
if (tx_filepath.empty()) {
return false;
}
return make_tx(filepath,
tx_filepath,
ccl::ustring(image->colorspace_settings.name),
ccl::ImageAlphaType(image->alpha_mode),
ccl::IMAGE_FORMAT_PLAIN);
}
} // namespace blender

View File

@@ -0,0 +1,821 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "BKE_colorband.hh"
#include "BKE_colortools.hh"
#include "BKE_main.hh"
#include "DNA_fluid_types.h"
#include "DNA_text_types.h"
#include "RE_engine.h"
#include "RNA_access.hh"
#include "scene/mesh.h"
#include "scene/scene.h"
#include "util/algorithm.h"
#include "util/array.h"
#include "util/path.h"
#include "util/set.h"
#include "util/transform.h"
#include "util/types.h"
#include "BLI_listbase.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_view3d_types.h"
#include "BKE_global.hh"
#include "BKE_image.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_types.hh"
#include "BKE_mesh_wrapper.hh"
#include "BKE_object.hh"
CCL_NAMESPACE_BEGIN
/* To make GS macro work. */
using ID_Type = blender::ID_Type;
static inline blender::ID *object_get_data(const blender::Object &b_ob,
const bool use_adaptive_subdivision)
{
if (!use_adaptive_subdivision && b_ob.type == blender::OB_MESH) {
return &BKE_mesh_wrapper_ensure_subdivision(blender::id_cast<blender::Mesh *>(b_ob.data))->id;
}
return reinterpret_cast<blender::ID *>(b_ob.data);
}
struct BObjectInfo {
/* Object directly provided by the depsgraph iterator. This object is only valid during one
* iteration and must not be accessed afterwards. Transforms and visibility should be checked on
* this object. */
blender::Object *iter_object;
/* This object remains alive even after the object iterator is done. It corresponds to one
* original object. It is the object that owns the object data below. */
blender::Object *real_object;
/* The object-data referenced by the iter object. This is still valid after the depsgraph
* iterator is done. It might have a different type compared to object_get_data(real_object). */
blender::ID *object_data;
/* Object will use adaptive subdivision. */
bool use_adaptive_subdivision;
/* True when the current geometry is the data of the referenced object. False when it is a
* geometry instance that does not have a 1-to-1 relationship with an object. */
bool is_real_object_data() const
{
return object_get_data(*real_object, use_adaptive_subdivision) == object_data;
}
};
static inline blender::Mesh *object_copy_mesh_data(const BObjectInfo &b_ob_info)
{
blender::Mesh *mesh = BKE_mesh_new_from_object(
nullptr, b_ob_info.real_object, false, false, !b_ob_info.use_adaptive_subdivision);
return mesh;
}
int blender_attribute_name_split_type(ustring name, string *r_real_name);
void python_thread_state_save(void **python_thread_state);
void python_thread_state_restore(void **python_thread_state);
static inline blender::Mesh *object_to_mesh(BObjectInfo &b_ob_info)
{
blender::Mesh *mesh = (GS(b_ob_info.object_data->name) == blender::ID_ME) ?
blender::id_cast<blender::Mesh *>(b_ob_info.object_data) :
nullptr;
if (b_ob_info.is_real_object_data()) {
if (mesh) {
if (mesh->runtime->edit_mesh) {
/* Flush edit-mesh to mesh, including all data layers. */
mesh = object_copy_mesh_data(b_ob_info);
}
}
else {
mesh = object_copy_mesh_data(b_ob_info);
}
}
else {
/* TODO: what to do about non-mesh geometry instances? */
}
if (mesh) {
if (b_ob_info.use_adaptive_subdivision) {
mesh->corner_tris();
}
}
return mesh;
}
static inline void free_object_to_mesh(BObjectInfo &b_ob_info, blender::Mesh &mesh)
{
if (!b_ob_info.is_real_object_data()) {
return;
}
/* Free mesh if we didn't just use the existing one. */
blender::Object *object = b_ob_info.real_object;
if (object_get_data(*object, b_ob_info.use_adaptive_subdivision) != &mesh.id) {
BKE_id_free(nullptr, &mesh.id);
}
}
static inline void colorramp_to_array(const blender::ColorBand &ramp,
array<packed_float3> &ramp_color,
array<float> &ramp_alpha,
const int size)
{
const int full_size = size + 1;
ramp_color.resize(full_size);
ramp_alpha.resize(full_size);
for (int i = 0; i < full_size; i++) {
float color[4];
BKE_colorband_evaluate(&ramp, float(i) / float(size), color);
ramp_color[i] = make_float3(color[0], color[1], color[2]);
ramp_alpha[i] = color[3];
}
}
static inline void curvemap_minmax_curve(const blender::CurveMap &curve,
float *min_x,
float *max_x)
{
const blender::Span<blender::CurveMapPoint> points(curve.curve, curve.totpoint);
*min_x = min(*min_x, points.first().x);
*max_x = max(*max_x, points.last().x);
}
static inline void curvemapping_minmax(const blender::CurveMapping &cumap,
const int num_curves,
float *min_x,
float *max_x)
{
// const int num_curves = cumap.curves.length(); /* Gives linking error so far. */
*min_x = FLT_MAX;
*max_x = -FLT_MAX;
for (int i = 0; i < num_curves; ++i) {
const blender::CurveMap &map(cumap.cm[i]);
curvemap_minmax_curve(map, min_x, max_x);
}
}
static inline void curvemapping_to_array(const blender::CurveMapping &cumap,
array<float> &data,
const int size)
{
BKE_curvemapping_changed_all(&const_cast<blender::CurveMapping &>(cumap));
const blender::CurveMap &curve = cumap.cm[0];
const int full_size = size + 1;
data.resize(full_size);
if (!curve.table) {
BKE_curvemapping_init(&const_cast<blender::CurveMapping &>(cumap));
}
for (int i = 0; i < full_size; i++) {
const float t = float(i) / float(size);
data[i] = BKE_curvemap_evaluateF(&cumap, &curve, t);
}
}
static inline void curvemapping_float_to_array(const blender::CurveMapping &cumap,
array<float> &data,
const int size)
{
float min = 0.0f;
float max = 1.0f;
curvemapping_minmax(cumap, 1, &min, &max);
const float range = max - min;
BKE_curvemapping_changed_all(&const_cast<blender::CurveMapping &>(cumap));
const blender::CurveMap &map = cumap.cm[0];
const int full_size = size + 1;
data.resize(full_size);
if (!map.table) {
BKE_curvemapping_init(&const_cast<blender::CurveMapping &>(cumap));
}
for (int i = 0; i < full_size; i++) {
const float t = min + float(i) / float(size) * range;
data[i] = BKE_curvemap_evaluateF(&cumap, &map, t);
}
}
static inline void curvemapping_color_to_array(const blender::CurveMapping &cumap,
array<packed_float3> &data,
const int size,
bool rgb_curve)
{
float min_x = 0.0f;
float max_x = 1.0f;
/* TODO(sergey): There is no easy way to automatically guess what is
* the range to be used here for the case when mapping is applied on
* top of another mapping (i.e. R curve applied on top of common
* one).
*
* Using largest possible range form all curves works correct for the
* cases like vector curves and should be good enough heuristic for
* the color curves as well.
*
* There might be some better estimations here tho.
*/
const int num_curves = rgb_curve ? 4 : 3;
curvemapping_minmax(cumap, num_curves, &min_x, &max_x);
const float range_x = max_x - min_x;
BKE_curvemapping_changed_all(&const_cast<blender::CurveMapping &>(cumap));
const blender::CurveMap &mapR = cumap.cm[0];
const blender::CurveMap &mapG = cumap.cm[1];
const blender::CurveMap &mapB = cumap.cm[2];
if (!mapR.table || !mapG.table || !mapB.table) {
BKE_curvemapping_init(&const_cast<blender::CurveMapping &>(cumap));
}
const int full_size = size + 1;
data.resize(full_size);
if (rgb_curve) {
const blender::CurveMap &mapI = cumap.cm[3];
if (!mapR.table || !mapG.table || !mapB.table || !mapI.table) {
BKE_curvemapping_init(&const_cast<blender::CurveMapping &>(cumap));
}
for (int i = 0; i < full_size; i++) {
const float t = min_x + float(i) / float(size) * range_x;
data[i] = make_float3(
BKE_curvemap_evaluateF(&cumap, &mapR, BKE_curvemap_evaluateF(&cumap, &mapI, t)),
BKE_curvemap_evaluateF(&cumap, &mapG, BKE_curvemap_evaluateF(&cumap, &mapI, t)),
BKE_curvemap_evaluateF(&cumap, &mapB, BKE_curvemap_evaluateF(&cumap, &mapI, t)));
}
}
else {
if (!mapR.table || !mapG.table || !mapB.table) {
BKE_curvemapping_init(&const_cast<blender::CurveMapping &>(cumap));
}
for (int i = 0; i < full_size; i++) {
const float t = min_x + float(i) / float(size) * range_x;
data[i] = make_float3(BKE_curvemap_evaluateF(&cumap, &mapR, t),
BKE_curvemap_evaluateF(&cumap, &mapG, t),
BKE_curvemap_evaluateF(&cumap, &mapB, t));
}
}
}
static inline bool BKE_object_is_deform_modified(BObjectInfo &self,
blender::Scene &scene,
bool preview)
{
if (!self.is_real_object_data()) {
/* Comes from geometry nodes, can't use heuristic to guess if it's animated. */
return true;
}
/* Use heuristic to quickly check if object is potentially animated. */
const int settings = preview ? blender::eModifierMode_Realtime : blender::eModifierMode_Render;
return (blender::BKE_object_is_deform_modified(&scene, self.real_object) & settings) != 0;
}
static inline int render_resolution_x(const blender::RenderData &b_render)
{
return b_render.xsch * b_render.size / 100;
}
static inline int render_resolution_y(const blender::RenderData &b_render)
{
return b_render.ysch * b_render.size / 100;
}
static inline string image_user_file_path(blender::Main &data,
blender::ImageUser &iuser,
blender::Image &ima,
const int cfra)
{
char filepath[1024];
BKE_image_user_frame_calc(&ima, &iuser, cfra);
BKE_image_user_file_path_ex(&data, &iuser, &ima, filepath, false, true);
return string(filepath);
}
static inline int image_user_frame_number(blender::ImageUser &iuser,
blender::Image &ima,
const int cfra)
{
BKE_image_user_frame_calc(&ima, &iuser, cfra);
return iuser.framenr;
}
static inline bool image_is_builtin(blender::Image &ima, blender::RenderEngine &engine)
{
const blender::eImageSource image_source = blender::eImageSource(ima.source);
if (image_source == blender::IMA_SRC_TILED) {
/* If any tile is marked as generated, then treat the entire Image as built-in. */
for (blender::ImageTile &tile : ima.tiles) {
if (tile.gen_flag & blender::IMA_GEN_TILE) {
return true;
}
}
}
return BKE_image_has_packedfile(&ima) || image_source == blender::IMA_SRC_GENERATED ||
image_source == blender::IMA_SRC_MOVIE || BKE_image_is_dirty(&ima) ||
((engine.flag & blender::RE_ENGINE_PREVIEW) != 0 &&
image_source != blender::IMA_SRC_SEQUENCE);
}
static inline void render_add_metadata(blender::RenderResult &b_rr, string name, string value)
{
BKE_render_result_stamp_data(&b_rr, name.c_str(), value.c_str());
}
/* Utilities */
static inline Transform get_transform(const blender::float4x4 &matrix)
{
/* Convert from Blender column major to Cycles row major, assume it's an affine transform that
* does not need the last row. */
const float *ptr = matrix.base_ptr();
return make_transform(ptr[0],
ptr[4],
ptr[8],
ptr[12],
ptr[1],
ptr[5],
ptr[9],
ptr[13],
ptr[2],
ptr[6],
ptr[10],
ptr[14]);
}
static inline float2 get_float2(blender::PointerRNA &ptr, const char *name)
{
float2 f;
RNA_float_get_array(&ptr, name, &f.x);
return f;
}
static inline void set_float2(blender::PointerRNA &ptr, const char *name, const float2 value)
{
RNA_float_set_array(&ptr, name, &value.x);
}
static inline float3 get_float3(blender::PointerRNA &ptr, const char *name)
{
float3 f;
RNA_float_get_array(&ptr, name, &f.x);
return f;
}
static inline void set_float3(blender::PointerRNA &ptr, const char *name, const float3 value)
{
RNA_float_set_array(&ptr, name, &value.x);
}
static inline float4 get_float4(blender::PointerRNA &ptr, const char *name)
{
float4 f;
RNA_float_get_array(&ptr, name, &f.x);
return f;
}
static inline void set_float4(blender::PointerRNA &ptr, const char *name, const float4 value)
{
RNA_float_set_array(&ptr, name, &value.x);
}
static inline bool get_boolean(blender::PointerRNA &ptr, const char *name)
{
return RNA_boolean_get(&ptr, name) ? true : false;
}
static inline void set_boolean(blender::PointerRNA &ptr, const char *name, bool value)
{
RNA_boolean_set(&ptr, name, (int)value);
}
static inline float get_float(blender::PointerRNA &ptr, const char *name)
{
return RNA_float_get(&ptr, name);
}
static inline void set_float(blender::PointerRNA &ptr, const char *name, const float value)
{
RNA_float_set(&ptr, name, value);
}
static inline int get_int(blender::PointerRNA &ptr, const char *name)
{
return RNA_int_get(&ptr, name);
}
static inline void set_int(blender::PointerRNA &ptr, const char *name, const int value)
{
RNA_int_set(&ptr, name, value);
}
/* Get a RNA enum value with sanity check: if the RNA value is above num_values
* the function will return a fallback default value.
*
* NOTE: This function assumes that RNA enum values are a continuous sequence
* from 0 to num_values-1. Be careful to use it with enums where some values are
* deprecated!
*/
static inline int get_enum(blender::PointerRNA &ptr,
const char *name,
int num_values = -1,
int default_value = -1)
{
int value = RNA_enum_get(&ptr, name);
if (num_values != -1 && value >= num_values) {
assert(default_value != -1);
value = default_value;
}
return value;
}
static inline string get_enum_identifier(blender::PointerRNA &ptr, const char *name)
{
blender::PropertyRNA *prop = RNA_struct_find_property(&ptr, name);
const char *identifier = "";
const int value = RNA_property_enum_get(&ptr, prop);
RNA_property_enum_identifier(nullptr, &ptr, prop, value, &identifier);
return string(identifier);
}
static inline void set_enum(blender::PointerRNA &ptr, const char *name, const int value)
{
RNA_enum_set(&ptr, name, value);
}
static inline void set_enum(blender::PointerRNA &ptr, const char *name, const string &identifier)
{
RNA_enum_set_identifier(nullptr, &ptr, name, identifier.c_str());
}
static inline string get_string(blender::PointerRNA &ptr, const char *name)
{
return RNA_string_get(&ptr, name);
}
static inline void set_string(blender::PointerRNA &ptr, const char *name, const string &value)
{
RNA_string_set(&ptr, name, value.c_str());
}
/* Relative Paths */
static inline string blender_absolute_path(blender::Main &b_data,
blender::ID *b_id,
const string &path)
{
if (path.size() >= 2 && path[0] == '/' && path[1] == '/') {
string dirname;
if (b_id && b_id->lib) {
dirname = blender_absolute_path(b_data, &b_id->lib->id, b_id->lib->filepath);
}
else {
dirname = b_data.filepath;
}
return path_join(path_dirname(dirname), path.substr(2));
}
return path;
}
static inline string get_text_datablock_content(const blender::ID *id)
{
if (id == nullptr) {
return "";
}
if (GS(id->name) != blender::ID_TXT) {
return "";
}
const auto &text = *blender::id_cast<const blender::Text *>(id);
string content;
for (blender::TextLine &line : text.lines) {
content += line.line ? line.line : "";
content += "\n";
}
return content;
}
/* Texture Space */
static inline void mesh_texture_space(const blender::Mesh &b_mesh, float3 &loc, float3 &size)
{
float texspace_location[3];
float texspace_size[3];
BKE_mesh_texspace_get(const_cast<blender::Mesh *>(&b_mesh), texspace_location, texspace_size);
loc = make_float3(texspace_location[0], texspace_location[1], texspace_location[2]);
size = make_float3(texspace_size[0], texspace_size[1], texspace_size[2]);
if (size.x != 0.0f) {
size.x = 0.5f / size.x;
}
if (size.y != 0.0f) {
size.y = 0.5f / size.y;
}
if (size.z != 0.0f) {
size.z = 0.5f / size.z;
}
loc = loc * size - make_float3(0.5f, 0.5f, 0.5f);
}
/* Object motion steps, returns 0 if no motion blur needed. */
static inline uint object_motion_steps(blender::Object &b_parent,
blender::Object &b_ob,
const int max_steps = INT_MAX)
{
/* Get motion enabled and steps from object itself. */
blender::PointerRNA object_rna_ptr = RNA_id_pointer_create(&b_ob.id);
blender::PointerRNA cobject = RNA_pointer_get(&object_rna_ptr, "cycles");
bool use_motion = get_boolean(cobject, "use_motion_blur");
if (!use_motion) {
return 0;
}
int steps = max(1, get_int(cobject, "motion_steps"));
/* Also check parent object, so motion blur and steps can be
* controlled by dupli-group duplicator for linked groups. */
if (&b_parent != &b_ob) {
blender::PointerRNA parent_rna_ptr = RNA_id_pointer_create(&b_parent.id);
blender::PointerRNA parent_cobject = RNA_pointer_get(&parent_rna_ptr, "cycles");
use_motion &= get_boolean(parent_cobject, "use_motion_blur");
if (!use_motion) {
return 0;
}
steps = max(steps, get_int(parent_cobject, "motion_steps"));
}
/* Use uneven number of steps so we get one keyframe at the current frame,
* and use 2^(steps - 1) so objects with more/fewer steps still have samples
* at the same times, to avoid sampling at many different times. */
return min((2 << (steps - 1)) + 1, max_steps);
}
/* object uses deformation motion blur */
static inline bool object_use_deform_motion(blender::Object &b_parent, blender::Object &b_ob)
{
blender::PointerRNA b_ob_rna_ptr = RNA_id_pointer_create(&b_ob.id);
blender::PointerRNA cobject = RNA_pointer_get(&b_ob_rna_ptr, "cycles");
bool use_deform_motion = get_boolean(cobject, "use_deform_motion");
/* If motion blur is enabled for the object we also check
* whether it's enabled for the parent object as well.
*
* This way we can control motion blur from the dupli-group
* duplicator much easier. */
if (use_deform_motion && &b_parent != &b_ob) {
blender::PointerRNA b_parent_rna_ptr = RNA_id_pointer_create(&b_parent.id);
blender::PointerRNA parent_cobject = RNA_pointer_get(&b_parent_rna_ptr, "cycles");
use_deform_motion &= get_boolean(parent_cobject, "use_deform_motion");
}
return use_deform_motion;
}
static inline blender::FluidDomainSettings *object_fluid_gas_domain_find(blender::Object &b_ob)
{
for (blender::ModifierData &b_mod : b_ob.modifiers) {
if (b_mod.type == blender::eModifierType_Fluid) {
auto *b_mmd = reinterpret_cast<blender::FluidModifierData *>(&b_mod);
if (b_mmd->type == blender::MOD_FLUID_TYPE_DOMAIN &&
b_mmd->domain->type == blender::FLUID_DOMAIN_TYPE_GAS)
{
return b_mmd->domain;
}
}
}
return nullptr;
}
static blender::SubsurfModifierData *object_subdivision_modifier(blender::Object &b_ob,
const bool preview)
{
blender::ModifierData *md = static_cast<blender::ModifierData *>(b_ob.modifiers.last);
if (!md) {
return nullptr;
}
if (md->type != blender::eModifierType_Subsurf) {
return nullptr;
}
const blender::ModifierMode enabled_mode = preview ? blender::eModifierMode_Realtime :
blender::eModifierMode_Render;
if ((md->mode & enabled_mode) == 0) {
return nullptr;
}
blender::SubsurfModifierData *subsurf = reinterpret_cast<blender::SubsurfModifierData *>(md);
if ((subsurf->flags & blender::eSubsurfModifierFlag_UseAdaptiveSubdivision) == 0) {
return nullptr;
}
return subsurf;
}
static inline Mesh::SubdivisionType object_subdivision_type(blender::Object &b_ob,
const bool preview,
const bool use_adaptive_subdivision)
{
if (!use_adaptive_subdivision) {
return Mesh::SUBDIVISION_NONE;
}
blender::SubsurfModifierData *subsurf = object_subdivision_modifier(b_ob, preview);
if (subsurf) {
if (subsurf->subdivType == blender::SUBSURF_TYPE_CATMULL_CLARK) {
return Mesh::SUBDIVISION_CATMULL_CLARK;
}
return Mesh::SUBDIVISION_LINEAR;
}
return Mesh::SUBDIVISION_NONE;
}
static inline void object_subdivision_to_mesh(blender::Object &b_ob,
Mesh &mesh,
const bool preview,
const bool use_adaptive_subdivision)
{
if (!use_adaptive_subdivision) {
mesh.set_subdivision_type(Mesh::SUBDIVISION_NONE);
return;
}
blender::SubsurfModifierData *subsurf = object_subdivision_modifier(b_ob, preview);
if (!subsurf) {
mesh.set_subdivision_type(Mesh::SUBDIVISION_NONE);
return;
}
if (subsurf->subdivType != blender::SUBSURF_TYPE_CATMULL_CLARK) {
mesh.set_subdivision_type(Mesh::SUBDIVISION_LINEAR);
return;
}
mesh.set_subdivision_type(Mesh::SUBDIVISION_CATMULL_CLARK);
switch (subsurf->boundary_smooth) {
case blender::SUBSURF_BOUNDARY_SMOOTH_PRESERVE_CORNERS:
mesh.set_subdivision_boundary_interpolation(Mesh::SUBDIVISION_BOUNDARY_EDGE_AND_CORNER);
break;
case blender::SUBSURF_BOUNDARY_SMOOTH_ALL:
mesh.set_subdivision_boundary_interpolation(Mesh::SUBDIVISION_BOUNDARY_EDGE_ONLY);
break;
}
switch (subsurf->uv_smooth) {
case blender::SUBSURF_UV_SMOOTH_NONE:
mesh.set_subdivision_fvar_interpolation(Mesh::SUBDIVISION_FVAR_LINEAR_ALL);
break;
case blender::SUBSURF_UV_SMOOTH_PRESERVE_CORNERS:
mesh.set_subdivision_fvar_interpolation(Mesh::SUBDIVISION_FVAR_LINEAR_CORNERS_ONLY);
break;
case blender::SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_AND_JUNCTIONS:
mesh.set_subdivision_fvar_interpolation(Mesh::SUBDIVISION_FVAR_LINEAR_CORNERS_PLUS1);
break;
case blender::SUBSURF_UV_SMOOTH_PRESERVE_CORNERS_JUNCTIONS_AND_CONCAVE:
mesh.set_subdivision_fvar_interpolation(Mesh::SUBDIVISION_FVAR_LINEAR_CORNERS_PLUS2);
break;
case blender::SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES:
mesh.set_subdivision_fvar_interpolation(Mesh::SUBDIVISION_FVAR_LINEAR_BOUNDARIES);
break;
case blender::SUBSURF_UV_SMOOTH_ALL:
mesh.set_subdivision_fvar_interpolation(Mesh::SUBDIVISION_FVAR_LINEAR_NONE);
break;
}
}
static inline PathRayVisibility object_ray_visibility(blender::Object &b_ob)
{
PathRayVisibility visibility = PATH_RAY_VISIBILITY_NONE;
visibility |= ((b_ob.visibility_flag & blender::OB_HIDE_CAMERA) == 0) ?
PATH_RAY_VISIBILITY_CAMERA :
PATH_RAY_VISIBILITY_NONE;
visibility |= ((b_ob.visibility_flag & blender::OB_HIDE_DIFFUSE) == 0) ?
PATH_RAY_VISIBILITY_DIFFUSE :
PATH_RAY_VISIBILITY_NONE;
visibility |= ((b_ob.visibility_flag & blender::OB_HIDE_GLOSSY) == 0) ?
PATH_RAY_VISIBILITY_GLOSSY :
PATH_RAY_VISIBILITY_NONE;
visibility |= ((b_ob.visibility_flag & blender::OB_HIDE_TRANSMISSION) == 0) ?
PATH_RAY_VISIBILITY_TRANSMIT :
PATH_RAY_VISIBILITY_NONE;
visibility |= ((b_ob.visibility_flag & blender::OB_HIDE_SHADOW) == 0) ?
PATH_RAY_VISIBILITY_SHADOW :
PATH_RAY_VISIBILITY_NONE;
visibility |= ((b_ob.visibility_flag & blender::OB_HIDE_VOLUME_SCATTER) == 0) ?
PATH_RAY_VISIBILITY_VOLUME_SCATTER :
PATH_RAY_VISIBILITY_NONE;
return visibility;
}
/* Check whether some of "built-in" motion-related attributes are needed to be exported (includes
* things like velocity from cache modifier, fluid simulation).
*
* NOTE: This code is run prior to object motion blur initialization. so can not access properties
* set by `sync_object_motion_init()`. */
static inline bool object_need_motion_attribute(BObjectInfo &b_ob_info, Scene *scene)
{
const Scene::MotionType need_motion = scene->need_motion();
if (need_motion == Scene::MOTION_NONE) {
/* Simple case: neither motion pass nor motion blur is needed, no need in the motion related
* attributes. */
return false;
}
if (need_motion == Scene::MOTION_BLUR) {
/* A bit tricky and implicit case:
* - Motion blur is enabled in the scene, which implies specific number of time steps for
* objects.
* - If the object has motion blur disabled on it, it will have 0 time steps.
* - Motion attribute expects non-zero time steps.
*
* Avoid adding motion attributes if the motion blur will enforce 0 motion steps. */
blender::PointerRNA b_ob_rna_ptr = RNA_id_pointer_create(&b_ob_info.real_object->id);
blender::PointerRNA cobject = RNA_pointer_get(&b_ob_rna_ptr, "cycles");
const bool use_motion = get_boolean(cobject, "use_motion_blur");
if (!use_motion) {
return false;
}
}
/* Motion pass which implies 3 motion steps, or motion blur which is not disabled on object
* level. */
return true;
}
static inline bool region_view3d_navigating_or_transforming(const blender::RegionView3D *b_rv3d)
{
return b_rv3d && ((b_rv3d->rflag & (blender::RV3D_NAVIGATING | blender::RV3D_PAINTING)) ||
(blender::G.moving & (blender::G_TRANSFORM_OBJ | blender::G_TRANSFORM_EDIT)));
}
class EdgeMap {
public:
EdgeMap() = default;
void clear()
{
edges_.clear();
}
void insert(int v0, int v1)
{
get_sorted_verts(v0, v1);
edges_.insert(std::pair<int, int>(v0, v1));
}
bool exists(int v0, int v1)
{
get_sorted_verts(v0, v1);
return edges_.contains(std::pair<int, int>(v0, v1));
}
protected:
void get_sorted_verts(int &v0, int &v1)
{
if (v0 > v1) {
swap(v0, v1);
}
}
set<std::pair<int, int>> edges_;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "blender/viewport.h"
#include "blender/util.h"
#include "scene/pass.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "RNA_prototypes.hh"
CCL_NAMESPACE_BEGIN
BlenderViewportParameters::BlenderViewportParameters()
: use_scene_world(true),
use_scene_lights(true),
studiolight_rotate_z(0.0f),
studiolight_intensity(1.0f),
studiolight_background_alpha(1.0f),
display_pass(PASS_COMBINED),
show_active_pixels(false)
{
}
BlenderViewportParameters::BlenderViewportParameters(blender::bScreen *b_screen,
blender::View3D *b_v3d,
bool use_developer_ui)
: BlenderViewportParameters()
{
if (!b_v3d) {
return;
}
blender::View3DShading shading = b_v3d->shading;
blender::PointerRNA v3d_rna_ptr = RNA_pointer_create_discrete(
&b_screen->id, blender::RNA_SpaceView3D, b_v3d);
blender::PointerRNA shading_rna_ptr = RNA_pointer_get(&v3d_rna_ptr, "shading");
/* We only copy the shading parameters if we are in look-dev mode.
* Otherwise defaults are being used. These defaults mimic normal render settings. */
if (shading.type == blender::OB_RENDER) {
use_scene_world = shading.flag & blender::V3D_SHADING_SCENE_WORLD_RENDER;
use_scene_lights = shading.flag & blender::V3D_SHADING_SCENE_LIGHTS_RENDER;
if (!use_scene_world) {
studiolight_rotate_z = shading.studiolight_rot_z;
studiolight_intensity = shading.studiolight_intensity;
studiolight_background_alpha = shading.studiolight_background;
blender::PointerRNA selected_studiolight_rna_ptr = RNA_pointer_get(&shading_rna_ptr,
"selected_studio_light");
studiolight_path = RNA_string_get(&selected_studiolight_rna_ptr, "path");
}
}
/* Film. */
/* Lookup display pass based on the enum identifier.
* This is because integer values of python enum are not aligned with the passes definition in
* the kernel. */
display_pass = PASS_COMBINED;
blender::PointerRNA cycles_shading_ptr = RNA_pointer_get(&shading_rna_ptr, "cycles");
const string display_pass_identifier = get_enum_identifier(cycles_shading_ptr, "render_pass");
if (!display_pass_identifier.empty()) {
const ustring pass_type_identifier(string_to_lower(display_pass_identifier));
const NodeEnum *pass_type_enum = Pass::get_type_enum();
if (pass_type_enum->exists(pass_type_identifier)) {
display_pass = static_cast<PassType>((*pass_type_enum)[pass_type_identifier]);
}
}
if (use_developer_ui) {
show_active_pixels = get_boolean(cycles_shading_ptr, "show_active_pixels");
}
}
bool BlenderViewportParameters::shader_modified(const BlenderViewportParameters &other) const
{
return use_scene_world != other.use_scene_world || use_scene_lights != other.use_scene_lights ||
studiolight_rotate_z != other.studiolight_rotate_z ||
studiolight_intensity != other.studiolight_intensity ||
studiolight_background_alpha != other.studiolight_background_alpha ||
studiolight_path != other.studiolight_path;
}
bool BlenderViewportParameters::film_modified(const BlenderViewportParameters &other) const
{
return display_pass != other.display_pass || show_active_pixels != other.show_active_pixels;
}
bool BlenderViewportParameters::modified(const BlenderViewportParameters &other) const
{
return shader_modified(other) || film_modified(other);
}
bool BlenderViewportParameters::use_custom_shader() const
{
return !(use_scene_world && use_scene_lights);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/types.h"
#include "util/param.h"
namespace blender {
struct View3D;
struct bScreen;
} // namespace blender
CCL_NAMESPACE_BEGIN
class BlenderViewportParameters {
public:
/* Shader. */
bool use_scene_world;
bool use_scene_lights;
float studiolight_rotate_z;
float studiolight_intensity;
float studiolight_background_alpha;
ustring studiolight_path;
/* Film. */
PassType display_pass;
bool show_active_pixels;
BlenderViewportParameters();
BlenderViewportParameters(blender::bScreen *b_screen,
blender::View3D *b_v3d,
bool use_developer_ui);
/* Check whether any of shading related settings are different from the given parameters. */
bool shader_modified(const BlenderViewportParameters &other) const;
/* Check whether any of film related settings are different from the given parameters. */
bool film_modified(const BlenderViewportParameters &other) const;
/* Check whether any of settings are different from the given parameters. */
bool modified(const BlenderViewportParameters &other) const;
/* Returns truth when a custom shader defined by the viewport is to be used instead of the
* regular background shader or scene light. */
bool use_custom_shader() const;
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,449 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/volume.h"
#include "scene/image.h"
#include "scene/image_vdb.h"
#include "scene/object.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "util/log.h"
#include "util/vector.h"
#include "BLI_bounds.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid.hh"
#include "DNA_cachefile_types.h"
#include "DNA_volume_types.h"
#include "RNA_prototypes.hh"
CCL_NAMESPACE_BEGIN
/* TODO: verify this is not loading unnecessary attributes. */
class BlenderSmokeLoader : public VDBImageLoader {
public:
BlenderSmokeLoader(blender::Object &b_ob, AttributeStandard attribute, const float clipping)
: VDBImageLoader(Attribute::standard_name(attribute), clipping),
b_domain(object_fluid_gas_domain_find(b_ob)),
attribute(attribute)
{
domain_rna_ptr = RNA_pointer_create_discrete(
&b_ob.id, blender::RNA_FluidDomainSettings, b_domain);
mesh_texture_space(
*blender::id_cast<const blender::Mesh *>(b_ob.data), texspace_loc, texspace_size);
}
void load_grid() override
{
if (!b_domain) {
return;
}
int channels;
if (attribute == ATTR_STD_VOLUME_DENSITY || attribute == ATTR_STD_VOLUME_FLAME ||
attribute == ATTR_STD_VOLUME_HEAT || attribute == ATTR_STD_VOLUME_TEMPERATURE)
{
channels = 1;
}
else if (attribute == ATTR_STD_VOLUME_COLOR) {
channels = 4;
}
else if (attribute == ATTR_STD_VOLUME_VELOCITY) {
channels = 3;
}
else {
return;
}
const int3 resolution = make_int3(b_domain->res[0], b_domain->res[1], b_domain->res[2]);
int amplify = (b_domain->flags & blender::FLUID_DOMAIN_USE_NOISE) != 0 ?
b_domain->noise_scale :
1;
/* Velocity and heat data is always low-resolution. */
if (attribute == ATTR_STD_VOLUME_VELOCITY || attribute == ATTR_STD_VOLUME_HEAT) {
amplify = 1;
}
const size_t width = resolution.x * amplify;
const size_t height = resolution.y * amplify;
const size_t depth = resolution.z * amplify;
/* Create a matrix to transform from object space to mesh texture space.
* This does not work with deformations but that can probably only be done
* well with a volume grid mapping of coordinates.
*
* Mantaflow stores values at cell centers, while OpenVDB convention is
* at corners. Offset by half a voxel to compensate for that. */
const float3 half_voxel = make_float3(0.5f / width, 0.5f / height, 0.5f / depth);
Transform transform_3d = transform_translate(-texspace_loc - half_voxel) *
transform_scale(texspace_size);
vector<float> voxels;
if (!get_voxels(width, height, depth, channels, voxels)) {
return;
}
grid_from_dense_voxels(width, height, depth, channels, voxels.data(), transform_3d);
}
bool get_voxels(const size_t width,
const size_t height,
const size_t depth,
const int channels,
vector<float> &voxels)
{
if (!b_domain) {
return false;
}
#ifdef WITH_FLUID
voxels.resize(width * height * depth * channels);
if (attribute == ATTR_STD_VOLUME_DENSITY) {
blender::PropertyRNA *prop = RNA_struct_find_property(&domain_rna_ptr, "density_grid");
if (RNA_property_array_length(&domain_rna_ptr, prop) == voxels.size()) {
RNA_property_float_get_array(&domain_rna_ptr, prop, voxels.data());
return true;
}
}
else if (attribute == ATTR_STD_VOLUME_FLAME) {
blender::PropertyRNA *prop = RNA_struct_find_property(&domain_rna_ptr, "flame_grid");
/* this is in range 0..1, and interpreted by the OpenGL smoke viewer
* as 1500..3000 K with the first part faded to zero density */
if (RNA_property_array_length(&domain_rna_ptr, prop) == voxels.size()) {
RNA_property_float_get_array(&domain_rna_ptr, prop, voxels.data());
return true;
}
}
else if (attribute == ATTR_STD_VOLUME_COLOR) {
blender::PropertyRNA *prop = RNA_struct_find_property(&domain_rna_ptr, "color_grid");
/* the RGB is "premultiplied" by density for better interpolation results */
if (RNA_property_array_length(&domain_rna_ptr, prop) == voxels.size()) {
RNA_property_float_get_array(&domain_rna_ptr, prop, voxels.data());
return true;
}
}
else if (attribute == ATTR_STD_VOLUME_VELOCITY) {
blender::PropertyRNA *prop = RNA_struct_find_property(&domain_rna_ptr, "velocity_grid");
if (RNA_property_array_length(&domain_rna_ptr, prop) == voxels.size()) {
RNA_property_float_get_array(&domain_rna_ptr, prop, voxels.data());
return true;
}
}
else if (attribute == ATTR_STD_VOLUME_HEAT) {
blender::PropertyRNA *prop = RNA_struct_find_property(&domain_rna_ptr, "heat_grid");
if (RNA_property_array_length(&domain_rna_ptr, prop) == voxels.size()) {
RNA_property_float_get_array(&domain_rna_ptr, prop, voxels.data());
return true;
}
}
else if (attribute == ATTR_STD_VOLUME_TEMPERATURE) {
blender::PropertyRNA *prop = RNA_struct_find_property(&domain_rna_ptr, "temperature_grid");
if (RNA_property_array_length(&domain_rna_ptr, prop) == voxels.size()) {
RNA_property_float_get_array(&domain_rna_ptr, prop, voxels.data());
return true;
}
}
else {
LOG_ERROR << "Unknown volume attribute " << Attribute::standard_name(attribute)
<< "skipping ";
voxels[0] = 0.0f;
return false;
}
LOG_ERROR << "Unexpected smoke volume resolution, skipping";
#else
(void)voxels;
(void)width;
(void)height;
(void)depth;
(void)channels;
#endif
return false;
}
string name() const override
{
return Attribute::standard_name(attribute);
}
bool equals(const ImageLoader &other) const override
{
const BlenderSmokeLoader &other_loader = (const BlenderSmokeLoader &)other;
return b_domain == other_loader.b_domain && attribute == other_loader.attribute;
}
blender::FluidDomainSettings *b_domain;
blender::PointerRNA domain_rna_ptr;
float3 texspace_loc, texspace_size;
AttributeStandard attribute;
};
static void sync_smoke_volume(blender::Scene &b_scene,
Scene *scene,
BObjectInfo &b_ob_info,
Volume *volume,
const float frame)
{
if (!b_ob_info.is_real_object_data()) {
return;
}
blender::FluidDomainSettings *b_domain = object_fluid_gas_domain_find(*b_ob_info.real_object);
if (!b_domain) {
return;
}
float velocity_scale = b_domain->velocity_scale;
/* Motion blur attribute is relative to seconds, we need it relative to frames. */
const bool need_motion = object_need_motion_attribute(b_ob_info, scene);
const float motion_scale = (need_motion) ? scene->motion_shutter_time() /
(b_scene.r.frs_sec / b_scene.r.frs_sec_base) :
0.0f;
velocity_scale *= motion_scale;
volume->set_velocity_scale(velocity_scale);
const AttributeStandard attributes[] = {ATTR_STD_VOLUME_DENSITY,
ATTR_STD_VOLUME_COLOR,
ATTR_STD_VOLUME_FLAME,
ATTR_STD_VOLUME_HEAT,
ATTR_STD_VOLUME_TEMPERATURE,
ATTR_STD_VOLUME_VELOCITY,
ATTR_STD_NONE};
const Interval<int> frame_interval = {b_domain->cache_frame_start, b_domain->cache_frame_end};
for (int i = 0; attributes[i] != ATTR_STD_NONE; i++) {
const AttributeStandard std = attributes[i];
if (!volume->need_attribute(scene, std)) {
continue;
}
const float clipping = b_domain->clipping;
Attribute *attr = volume->attributes.add(std);
if (!frame_interval.contains(frame)) {
attr->data_voxel_for_write().clear();
continue;
}
unique_ptr<ImageLoader> loader = make_unique<BlenderSmokeLoader>(
*b_ob_info.real_object, std, clipping);
ImageParams params;
params.frame = frame;
attr->data_voxel_for_write() = scene->image_manager->add_image(std::move(loader), params);
}
/* Create a matrix to transform from object space to normalized texture space [0, 1]. */
if (volume->need_attribute(scene, ATTR_STD_GENERATED_TRANSFORM)) {
const blender::Mesh &b_mesh = *blender::id_cast<const blender::Mesh *>(b_ob_info.object_data);
float3 loc;
float3 size;
mesh_texture_space(b_mesh, loc, size);
Attribute *attr = volume->attributes.add(ATTR_STD_GENERATED_TRANSFORM);
Transform *tfm = attr->data_for_write<Transform>();
*tfm = transform_translate(-loc) * transform_scale(size);
}
}
class BlenderVolumeLoader : public VDBImageLoader {
public:
BlenderVolumeLoader(blender::Main &b_data,
blender::Volume &b_volume,
const string &grid_name,
blender::VolumeRenderPrecision precision_,
const float clipping)
: VDBImageLoader(grid_name, clipping), b_volume(b_volume)
{
BKE_volume_load(&b_volume, &b_data);
#ifdef WITH_OPENVDB
for (const int grid_index : blender::IndexRange(BKE_volume_num_grids(&b_volume))) {
const blender::bke::VolumeGridData &b_volume_grid = *BKE_volume_grid_get(&b_volume,
grid_index);
if (b_volume_grid.name() == grid_name) {
b_volume_grid.add_user();
volume_grid = blender::bke::GVolumeGrid{&b_volume_grid};
grid = volume_grid->grid_ptr(tree_access_token);
break;
}
}
#endif
#ifdef WITH_NANOVDB
switch (precision_) {
case blender::VOLUME_PRECISION_FULL:
precision = 32;
break;
case blender::VOLUME_PRECISION_HALF:
precision = 16;
break;
default:
case blender::VOLUME_PRECISION_VARIABLE:
precision = 0;
break;
}
#else
(void)precision_;
#endif
}
blender::Volume &b_volume;
#ifdef WITH_OPENVDB
/* Store tree user so that the OPENVDB grid that is shared with Blender is not unloaded. */
blender::bke::GVolumeGrid volume_grid;
blender::bke::VolumeTreeAccessToken tree_access_token;
#endif
};
static void sync_volume_object(blender::Main &b_data,
blender::Scene &b_scene,
BObjectInfo &b_ob_info,
Scene *scene,
Volume *volume)
{
blender::Volume &b_volume = *blender::id_cast<blender::Volume *>(b_ob_info.object_data);
BKE_volume_load(&b_volume, &b_data);
blender::VolumeRender &b_render = b_volume.render;
volume->set_step_size(b_render.step_size);
volume->set_object_space((b_render.space == blender::VOLUME_SPACE_OBJECT));
float velocity_scale = b_volume.velocity_scale;
if (b_volume.velocity_unit == blender::CACHEFILE_VELOCITY_UNIT_SECOND) {
/* Motion blur attribute is relative to seconds, we need it relative to frames. */
const bool need_motion = object_need_motion_attribute(b_ob_info, scene);
const float motion_scale = (need_motion) ? scene->motion_shutter_time() /
(b_scene.r.frs_sec / b_scene.r.frs_sec_base) :
0.0f;
velocity_scale *= motion_scale;
}
volume->set_velocity_scale(velocity_scale);
#ifdef WITH_OPENVDB
/* Find grid with matching name. */
for (const int grid_index : blender::IndexRange(BKE_volume_num_grids(&b_volume))) {
const blender::bke::VolumeGridData &b_grid = *BKE_volume_grid_get(&b_volume, grid_index);
const ustring name = ustring(b_grid.name());
AttributeStandard std = ATTR_STD_NONE;
if (name == Attribute::standard_name(ATTR_STD_VOLUME_DENSITY)) {
std = ATTR_STD_VOLUME_DENSITY;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_COLOR)) {
std = ATTR_STD_VOLUME_COLOR;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_FLAME)) {
std = ATTR_STD_VOLUME_FLAME;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_HEAT)) {
std = ATTR_STD_VOLUME_HEAT;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_TEMPERATURE)) {
std = ATTR_STD_VOLUME_TEMPERATURE;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY) ||
name == b_volume.velocity_grid)
{
std = ATTR_STD_VOLUME_VELOCITY;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_X) ||
name == b_volume.runtime->velocity_x_grid)
{
std = ATTR_STD_VOLUME_VELOCITY_X;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_Y) ||
name == b_volume.runtime->velocity_y_grid)
{
std = ATTR_STD_VOLUME_VELOCITY_Y;
}
else if (name == Attribute::standard_name(ATTR_STD_VOLUME_VELOCITY_Z) ||
name == b_volume.runtime->velocity_z_grid)
{
std = ATTR_STD_VOLUME_VELOCITY_Z;
}
const bool need_std = (std != ATTR_STD_NONE && volume->need_attribute(scene, std));
const bool need_named = volume->need_attribute(scene, name);
if (need_std || need_named) {
Attribute *attr;
if (need_std) {
/* Make grid available both with std and name. */
attr = volume->attributes.add(std, name);
}
else {
/* Make grid available by name with appropriate number of channels. */
const int channels = blender::bke::volume_grid::get_channels_num(b_grid.grid_type());
const TypeDesc type = (channels == 3) ? TypeVector : TypeFloat;
attr = volume->attributes.add(name, type, ATTR_ELEMENT_VOXEL);
}
unique_ptr<ImageLoader> loader = make_unique<BlenderVolumeLoader>(
b_data,
b_volume,
name.string(),
blender::VolumeRenderPrecision(b_render.precision),
b_render.clipping);
ImageParams params;
params.frame = b_volume.runtime->frame;
attr->data_voxel_for_write() = scene->image_manager->add_image(
std::move(loader), params, false);
}
}
#endif
/* Create a matrix to transform from object space to normalized texture space [0, 1]. */
if (volume->need_attribute(scene, ATTR_STD_GENERATED_TRANSFORM)) {
std::optional<const blender::Bounds<blender::float3>> bounds = BKE_volume_min_max(&b_volume);
if (bounds.has_value()) {
const blender::float3 size = bounds->size();
const float3 loc = make_float3(bounds->min[0], bounds->min[1], bounds->min[2]);
const float3 inv_size = safe_divide(one_float3(), make_float3(size[0], size[1], size[2]));
Attribute *attr = volume->attributes.add(ATTR_STD_GENERATED_TRANSFORM);
Transform *tfm = attr->data_for_write<Transform>();
*tfm = transform_scale(inv_size) * transform_translate(-loc);
}
}
}
void BlenderSync::sync_volume(BObjectInfo &b_ob_info, Volume *volume)
{
volume->clear(true);
if (view_layer.use_volumes) {
if (GS(b_ob_info.object_data->name) == blender::ID_VO) {
/* Volume object. Create only attributes, bounding mesh will then
* be automatically generated later. */
sync_volume_object(*b_data, *b_scene, b_ob_info, scene, volume);
}
else {
/* Smoke domain. */
sync_smoke_volume(*b_scene, scene, b_ob_info, volume, b_scene->r.cfra);
}
}
volume->merge_grids(scene);
/* Tag update. */
volume->tag_update(scene, true);
}
CCL_NAMESPACE_END