WIP: Basic support for registering asset shelf as a type in BPY #104991

Closed
Julian Eisel wants to merge 73 commits from JulianEisel:temp-asset-shelf-type-bpy into asset-shelf

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
112 changed files with 2740 additions and 675 deletions
Showing only changes of commit af0c1d72a2 - Show all commits

View File

@ -69,6 +69,7 @@ Thanks to Tyler Alden Gubala for maintaining the original version of this packag
# ------------------------------------------------------------------------------
# Generic Functions
def find_dominating_file(
path: str,
search: Sequence[str],

View File

@ -204,7 +204,6 @@ def list_render_passes(scene, srl):
if crl.use_pass_volume_indirect: yield ("VolumeInd", "RGB", 'COLOR')
if srl.use_pass_emit: yield ("Emit", "RGB", 'COLOR')
if srl.use_pass_environment: yield ("Env", "RGB", 'COLOR')
if srl.use_pass_shadow: yield ("Shadow", "RGB", 'COLOR')
if srl.use_pass_ambient_occlusion: yield ("AO", "RGB", 'COLOR')
if crl.use_pass_shadow_catcher: yield ("Shadow Catcher", "RGB", 'COLOR')
# autopep8: on

View File

@ -87,11 +87,26 @@ enum_sampling_pattern = (
)
enum_emission_sampling = (
('NONE', 'None', "Do not use this surface as a light for sampling", 0),
('AUTO', 'Auto', "Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity", 1),
('FRONT', 'Front', "Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible", 2),
('BACK', 'Back', "Treat only back side of the surface as a light for sampling", 3),
('FRONT_BACK', 'Front and Back', "Treat surface as a light for sampling, emitting from both the front and back side", 4),
('NONE',
'None',
"Do not use this surface as a light for sampling",
0),
('AUTO',
'Auto',
"Automatically determine if the surface should be treated as a light for sampling, based on estimated emission intensity",
1),
('FRONT',
'Front',
"Treat only front side of the surface as a light, usually for closed meshes whose interior is not visible",
2),
('BACK',
'Back',
"Treat only back side of the surface as a light for sampling",
3),
('FRONT_BACK',
'Front and Back',
"Treat surface as a light for sampling, emitting from both the front and back side",
4),
)
enum_volume_sampling = (
@ -155,7 +170,6 @@ enum_view3d_shading_render_pass = (
('EMISSION', "Emission", "Show the Emission render pass"),
('BACKGROUND', "Background", "Show the Background render pass"),
('AO', "Ambient Occlusion", "Show the Ambient Occlusion render pass"),
('SHADOW', "Shadow", "Show the Shadow render pass"),
('SHADOW_CATCHER', "Shadow Catcher", "Show the Shadow Catcher render pass"),
('', "Light", ""),
@ -489,6 +503,12 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default='MULTIPLE_IMPORTANCE_SAMPLING',
)
use_light_tree: BoolProperty(
name="Light Tree",
description="Sample multiple lights more efficiently based on estimated contribution at every shading point",
default=True,
)
min_light_bounces: IntProperty(
name="Min Light Bounces",
description="Minimum number of light bounces. Setting this higher reduces noise in the first bounces, "
@ -630,7 +650,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
transparent_max_bounces: IntProperty(
name="Transparent Max Bounces",
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces ",
description="Maximum number of transparent bounces. This is independent of maximum number of other bounces",
min=0, max=1024,
default=8,
)

View File

@ -383,7 +383,6 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
col = layout.column(align=True)
col.prop(cscene, "min_light_bounces")
col.prop(cscene, "min_transparent_bounces")
col.prop(cscene, "light_sampling_threshold", text="Light Threshold")
for view_layer in scene.view_layers:
if view_layer.samples > 0:
@ -392,6 +391,31 @@ class CYCLES_RENDER_PT_sampling_advanced(CyclesButtonsPanel, Panel):
break
class CYCLES_RENDER_PT_sampling_lights(CyclesButtonsPanel, Panel):
bl_label = "Lights"
bl_parent_id = "CYCLES_RENDER_PT_sampling"
bl_options = {'DEFAULT_CLOSED'}
def draw_header(self, context):
layout = self.layout
scene = context.scene
cscene = scene.cycles
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
scene = context.scene
cscene = scene.cycles
col = layout.column(align=True)
#col.prop(cscene, "use_light_tree")
sub = col.row()
sub.prop(cscene, "light_sampling_threshold", text="Light Threshold")
#sub.active = not cscene.use_light_tree
class CYCLES_RENDER_PT_subdivision(CyclesButtonsPanel, Panel):
bl_label = "Subdivision"
bl_options = {'DEFAULT_CLOSED'}
@ -954,7 +978,6 @@ class CYCLES_RENDER_PT_passes_light(CyclesButtonsPanel, Panel):
col = layout.column(heading="Other", align=True)
col.prop(view_layer, "use_pass_emit", text="Emission")
col.prop(view_layer, "use_pass_environment")
col.prop(view_layer, "use_pass_shadow")
col.prop(view_layer, "use_pass_ambient_occlusion", text="Ambient Occlusion")
col.prop(cycles_view_layer, "use_pass_shadow_catcher")
@ -2366,6 +2389,7 @@ classes = (
CYCLES_RENDER_PT_sampling_render_denoise,
CYCLES_RENDER_PT_sampling_path_guiding,
CYCLES_RENDER_PT_sampling_path_guiding_debug,
CYCLES_RENDER_PT_sampling_lights,
CYCLES_RENDER_PT_sampling_advanced,
CYCLES_RENDER_PT_light_paths,
CYCLES_RENDER_PT_light_paths_max_bounces,

View File

@ -803,7 +803,7 @@ void BlenderDisplayDriver::draw(const Params &params)
const int position_attribute = GPU_vertformat_attr_add(
format, display_shader_->position_attribute_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
/* Note: Shader is bound again through IMM to register this shader with the imm module
/* 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. */

View File

@ -126,7 +126,7 @@ class BlenderDisplayDriver : public DisplayDriver {
void gpu_context_lock();
void gpu_context_unlock();
/* Create GPU resources used by the dispaly driver. */
/* Create GPU resources used by the display driver. */
bool gpu_resources_create();
/* Destroy all GPU resources which are being used by this object. */

View File

@ -559,11 +559,6 @@ static bool bake_setup_pass(Scene *scene, const string &bake_type_str, const int
0);
integrator->set_use_emission((bake_filter & BL::BakeSettings::pass_filter_EMIT) != 0);
}
/* Shadow pass. */
else if (strcmp(bake_type, "SHADOW") == 0) {
type = PASS_SHADOW;
use_direct_light = true;
}
/* Light component passes. */
else if (strcmp(bake_type, "DIFFUSE") == 0) {
if ((bake_filter & BL::BakeSettings::pass_filter_DIRECT) &&

View File

@ -347,7 +347,14 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer, bool background)
integrator->set_motion_blur(view_layer.use_motion_blur);
}
integrator->set_light_sampling_threshold(get_float(cscene, "light_sampling_threshold"));
bool use_light_tree = false; // get_boolean(cscene, "use_light_tree");
integrator->set_use_light_tree(use_light_tree);
integrator->set_light_sampling_threshold(
(use_light_tree) ? 0.0f : get_float(cscene, "light_sampling_threshold"));
if (integrator->use_light_tree_is_modified()) {
scene->light_manager->tag_update(scene, LightManager::UPDATE_ALL);
}
SamplingPattern sampling_pattern = (SamplingPattern)get_enum(
cscene, "sampling_pattern", SAMPLING_NUM_PATTERNS, SAMPLING_PATTERN_PMJ);
@ -616,7 +623,6 @@ static bool get_known_pass_type(BL::RenderPass &b_pass, PassType &type, PassMode
MAP_PASS("Emit", PASS_EMISSION, false);
MAP_PASS("Env", PASS_BACKGROUND, false);
MAP_PASS("AO", PASS_AO, false);
MAP_PASS("Shadow", PASS_SHADOW, false);
MAP_PASS("BakePrimitive", PASS_BAKE_PRIMITIVE, false);
MAP_PASS("BakeDifferential", PASS_BAKE_DIFFERENTIAL, false);

View File

@ -351,6 +351,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
info.num = 0;
info.has_nanovdb = true;
info.has_light_tree = true;
info.has_osl = true;
info.has_guiding = true;
info.has_profiling = true;
@ -399,6 +400,7 @@ DeviceInfo Device::get_multi_device(const vector<DeviceInfo> &subdevices,
/* Accumulate device info. */
info.has_nanovdb &= device.has_nanovdb;
info.has_light_tree &= device.has_light_tree;
info.has_osl &= device.has_osl;
info.has_guiding &= device.has_guiding;
info.has_profiling &= device.has_profiling;

View File

@ -65,6 +65,7 @@ class DeviceInfo {
int num;
bool display_device; /* GPU is used as a display device. */
bool has_nanovdb; /* Support NanoVDB volumes. */
bool has_light_tree; /* Support light tree. */
bool has_osl; /* Support Open Shading Language. */
bool has_guiding; /* Support path guiding. */
bool has_profiling; /* Supports runtime collection of profiling info. */
@ -84,6 +85,7 @@ class DeviceInfo {
cpu_threads = 0;
display_device = false;
has_nanovdb = false;
has_light_tree = true;
has_osl = false;
has_guiding = false;
has_profiling = false;

View File

@ -137,6 +137,7 @@ void device_hip_info(vector<DeviceInfo> &devices)
info.num = num;
info.has_nanovdb = true;
info.has_light_tree = false;
info.denoisers = 0;
info.has_gpu_queue = true;

View File

@ -294,6 +294,7 @@ set(SRC_KERNEL_LIGHT_HEADERS
light/point.h
light/sample.h
light/spot.h
light/tree.h
light/triangle.h
)

View File

@ -60,6 +60,13 @@ KERNEL_DATA_ARRAY(KernelLight, lights)
KERNEL_DATA_ARRAY(float2, light_background_marginal_cdf)
KERNEL_DATA_ARRAY(float2, light_background_conditional_cdf)
/* light tree */
KERNEL_DATA_ARRAY(KernelLightTreeNode, light_tree_nodes)
KERNEL_DATA_ARRAY(KernelLightTreeEmitter, light_tree_emitters)
KERNEL_DATA_ARRAY(uint, light_to_tree)
KERNEL_DATA_ARRAY(uint, object_lookup_offset)
KERNEL_DATA_ARRAY(uint, triangle_to_tree)
/* particles */
KERNEL_DATA_ARRAY(KernelParticle, particles)

View File

@ -97,8 +97,6 @@ KERNEL_STRUCT_MEMBER(film, int, pass_emission)
KERNEL_STRUCT_MEMBER(film, int, pass_background)
KERNEL_STRUCT_MEMBER(film, int, pass_ao)
KERNEL_STRUCT_MEMBER(film, float, pass_alpha_threshold)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow)
KERNEL_STRUCT_MEMBER(film, float, pass_shadow_scale)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_sample_count)
KERNEL_STRUCT_MEMBER(film, int, pass_shadow_catcher_matte)
@ -132,9 +130,6 @@ KERNEL_STRUCT_MEMBER(film, int, use_approximate_shadow_catcher)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_color)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_probability)
KERNEL_STRUCT_MEMBER(film, int, pass_guiding_avg_roughness)
/* Padding. */
KERNEL_STRUCT_MEMBER(film, int, pad1)
KERNEL_STRUCT_MEMBER(film, int, pad2)
KERNEL_STRUCT_END(KernelFilm)
/* Integrator. */
@ -143,6 +138,7 @@ KERNEL_STRUCT_BEGIN(KernelIntegrator, integrator)
/* Emission. */
KERNEL_STRUCT_MEMBER(integrator, int, use_direct_light)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_mis)
KERNEL_STRUCT_MEMBER(integrator, int, use_light_tree)
KERNEL_STRUCT_MEMBER(integrator, int, num_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_distant_lights)
KERNEL_STRUCT_MEMBER(integrator, int, num_background_lights)
@ -209,7 +205,6 @@ KERNEL_STRUCT_MEMBER(integrator, int, use_guiding_mis_weights)
/* Padding. */
KERNEL_STRUCT_MEMBER(integrator, int, pad1)
KERNEL_STRUCT_MEMBER(integrator, int, pad2)
KERNEL_STRUCT_MEMBER(integrator, int, pad3)
KERNEL_STRUCT_END(KernelIntegrator)
/* SVM. For shader specialization. */

View File

@ -527,17 +527,6 @@ ccl_device_inline void film_write_direct_light(KernelGlobals kg,
film_write_pass_spectrum(buffer + pass_offset, contribution);
}
}
/* Write shadow pass. */
if (kernel_data.film.pass_shadow != PASS_UNUSED && (path_flag & PATH_RAY_SHADOW_FOR_LIGHT) &&
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
const Spectrum unshadowed_throughput = INTEGRATOR_STATE(
state, shadow_path, unshadowed_throughput);
const Spectrum shadowed_throughput = INTEGRATOR_STATE(state, shadow_path, throughput);
const Spectrum shadow = safe_divide(shadowed_throughput, unshadowed_throughput) *
kernel_data.film.pass_shadow_scale;
film_write_pass_spectrum(buffer + kernel_data.film.pass_shadow, shadow);
}
}
#endif
}

View File

@ -113,7 +113,7 @@ ccl_device_inline void integrate_background(KernelGlobals kg,
/* Background MIS weights. */
float mis_weight = 1.0f;
/* Check if background light exists or if we should skip pdf. */
/* Check if background light exists or if we should skip PDF. */
if (!(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_MIS_SKIP) &&
kernel_data.background.use_mis) {
mis_weight = light_sample_mis_weight_forward_background(kg, state, path_flag);

View File

@ -326,10 +326,6 @@ ccl_device_forceinline void integrate_surface_direct_light(KernelGlobals kg,
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput;
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
}
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
INTEGRATOR_STATE_WRITE(
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
@ -445,6 +441,7 @@ ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
/* Update path state */
if (!(label & LABEL_TRANSPARENT)) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = bsdf_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N;
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_bsdf_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
}

View File

@ -685,15 +685,14 @@ ccl_device_forceinline void volume_integrate_heterogeneous(
# endif /* __DENOISING_FEATURES__ */
}
/* Path tracing: sample point on light and evaluate light shader, then
* queue shadow ray to be traced. */
ccl_device_forceinline bool integrate_volume_sample_light(
/* Path tracing: sample point on light for equiangular sampling. */
ccl_device_forceinline bool integrate_volume_equiangular_sample_light(
KernelGlobals kg,
IntegratorState state,
ccl_private const Ray *ccl_restrict ray,
ccl_private const ShaderData *ccl_restrict sd,
ccl_private const RNGState *ccl_restrict rng_state,
ccl_private LightSample *ccl_restrict ls)
ccl_private float3 *ccl_restrict P)
{
/* Test if there is a light or BSDF that needs direct light. */
if (!kernel_data.integrator.use_direct_light) {
@ -705,6 +704,7 @@ ccl_device_forceinline bool integrate_volume_sample_light(
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float2 rand_light = path_state_rng_2D(kg, rng_state, PRNG_LIGHT);
LightSample ls ccl_optional_struct_init;
if (!light_sample_from_volume_segment(kg,
rand_light.x,
rand_light.y,
@ -714,14 +714,20 @@ ccl_device_forceinline bool integrate_volume_sample_light(
ray->tmax - ray->tmin,
bounce,
path_flag,
ls)) {
&ls)) {
return false;
}
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
return false;
}
if (ls.t == FLT_MAX) {
return false;
}
*P = ls.P;
return true;
}
@ -737,8 +743,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
# ifdef __PATH_GUIDING__
ccl_private const Spectrum unlit_throughput,
# endif
ccl_private const Spectrum throughput,
ccl_private LightSample *ccl_restrict ls)
ccl_private const Spectrum throughput)
{
PROFILING_INIT(kg, PROFILING_SHADE_VOLUME_DIRECT_LIGHT);
@ -756,6 +761,7 @@ ccl_device_forceinline void integrate_volume_direct_light(
* Additionally we could end up behind the light or outside a spot light cone, which might
* waste a sample. Though on the other hand it would be possible to prevent that with
* equiangular sampling restricted to a smaller sub-segment where the light has influence. */
LightSample ls ccl_optional_struct_init;
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
@ -771,12 +777,12 @@ ccl_device_forceinline void integrate_volume_direct_light(
SD_BSDF_HAS_TRANSMISSION,
bounce,
path_flag,
ls)) {
&ls)) {
return;
}
}
if (ls->shader & SHADER_EXCLUDE_SCATTER) {
if (ls.shader & SHADER_EXCLUDE_SCATTER) {
return;
}
@ -788,32 +794,32 @@ ccl_device_forceinline void integrate_volume_direct_light(
* non-constant light sources. */
ShaderDataTinyStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, ls, sd->time);
const Spectrum light_eval = light_sample_shader_eval(kg, state, emission_sd, &ls, sd->time);
if (is_zero(light_eval)) {
return;
}
/* Evaluate BSDF. */
BsdfEval phase_eval ccl_optional_struct_init;
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls->D, &phase_eval);
float phase_pdf = volume_shader_phase_eval(kg, state, sd, phases, ls.D, &phase_eval);
if (ls->shader & SHADER_USE_MIS) {
float mis_weight = light_sample_mis_weight_nee(kg, ls->pdf, phase_pdf);
if (ls.shader & SHADER_USE_MIS) {
float mis_weight = light_sample_mis_weight_nee(kg, ls.pdf, phase_pdf);
bsdf_eval_mul(&phase_eval, mis_weight);
}
bsdf_eval_mul(&phase_eval, light_eval / ls->pdf);
bsdf_eval_mul(&phase_eval, light_eval / ls.pdf);
/* Path termination. */
const float terminate = path_state_rng_light_termination(kg, rng_state);
if (light_sample_terminate(kg, ls, &phase_eval, terminate)) {
if (light_sample_terminate(kg, &ls, &phase_eval, terminate)) {
return;
}
/* Create shadow ray. */
Ray ray ccl_optional_struct_init;
light_sample_to_volume_shadow_ray(kg, sd, ls, P, &ray);
const bool is_light = light_sample_is_light(ls);
light_sample_to_volume_shadow_ray(kg, sd, &ls, P, &ray);
const bool is_light = light_sample_is_light(&ls);
/* Branch off shadow kernel. */
IntegratorShadowState shadow_state = integrator_shadow_path_init(
@ -872,14 +878,10 @@ ccl_device_forceinline void integrate_volume_direct_light(
state, path, transmission_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput_phase;
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_PASS) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = throughput;
}
/* Write Lightgroup, +1 as lightgroup is int but we need to encode into a uint8_t. */
INTEGRATOR_STATE_WRITE(
shadow_state, shadow_path, lightgroup) = (ls->type != LIGHT_BACKGROUND) ?
ls->group + 1 :
shadow_state, shadow_path, lightgroup) = (ls.type != LIGHT_BACKGROUND) ?
ls.group + 1 :
kernel_data.background.lightgroup + 1;
# ifdef __PATH_GUIDING__
@ -981,6 +983,7 @@ ccl_device_forceinline bool integrate_volume_phase_scatter(
/* Update path state */
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = phase_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = zero_float3();
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(
unguided_phase_pdf, INTEGRATOR_STATE(state, path, min_ray_pdf));
@ -1006,12 +1009,11 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
/* Sample light ahead of volume stepping, for equiangular sampling. */
/* TODO: distant lights are ignored now, but could instead use even distribution. */
LightSample ls ccl_optional_struct_init;
const bool need_light_sample = !(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_TERMINATE);
float3 equiangular_P = zero_float3();
const bool have_equiangular_sample = need_light_sample &&
integrate_volume_sample_light(
kg, state, ray, &sd, &rng_state, &ls) &&
(ls.t != FLT_MAX);
integrate_volume_equiangular_sample_light(
kg, state, ray, &sd, &rng_state, &equiangular_P);
VolumeSampleMethod direct_sample_method = (have_equiangular_sample) ?
volume_stack_sample_method(kg, state) :
@ -1041,7 +1043,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
render_buffer,
step_size,
direct_sample_method,
ls.P,
equiangular_P,
result);
/* Perform path termination. The intersect_closest will have already marked this path
@ -1108,8 +1110,7 @@ ccl_device VolumeIntegrateEvent volume_integrate(KernelGlobals kg,
# ifdef __PATH_GUIDING__
unlit_throughput,
# endif
result.direct_throughput,
&ls);
result.direct_throughput);
}
/* Indirect light.

View File

@ -32,7 +32,7 @@ KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, throughput, KERNEL_FEATURE_PAT
KERNEL_STRUCT_MEMBER(shadow_path,
PackedSpectrum,
unshadowed_throughput,
KERNEL_FEATURE_SHADOW_PASS | KERNEL_FEATURE_AO_ADDITIVE)
KERNEL_FEATURE_AO_ADDITIVE)
/* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES)
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)

View File

@ -41,6 +41,7 @@ KERNEL_STRUCT_MEMBER(path, uint8_t, mnee, KERNEL_FEATURE_PATH_TRACING)
* zero and distance. Note that transparency and volume attenuation increase
* the ray tmin but keep P unmodified so that this works. */
KERNEL_STRUCT_MEMBER(path, float, mis_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER(path, packed_float3, mis_origin_n, KERNEL_FEATURE_PATH_TRACING)
/* Filter glossy. */
KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
/* Continuation probability for path termination. */

View File

@ -91,7 +91,7 @@ ccl_device_inline float area_light_rect_sample(float3 P,
ccl_device float area_light_spread_attenuation(const float3 D,
const float3 lightNg,
const float tan_spread,
const float cot_half_spread,
const float normalize_spread)
{
/* Model a soft-box grid, computing the ratio of light not hidden by the
@ -99,7 +99,7 @@ ccl_device float area_light_spread_attenuation(const float3 D,
const float cos_a = -dot(D, lightNg);
const float sin_a = safe_sqrtf(1.0f - sqr(cos_a));
const float tan_a = sin_a / cos_a;
return max((1.0f - (tan_spread * tan_a)) * normalize_spread, 0.0f);
return max((1.0f - (cot_half_spread * tan_a)) * normalize_spread, 0.0f);
}
/* Compute subset of area light that actually has an influence on the shading point, to
@ -111,14 +111,14 @@ ccl_device bool area_light_spread_clamp_area_light(const float3 P,
ccl_private float *len_u,
const float3 axis_v,
ccl_private float *len_v,
const float tan_spread)
const float cot_half_spread)
{
/* Closest point in area light plane and distance to that plane. */
const float3 closest_P = P - dot(lightNg, P - *lightP) * lightNg;
const float t = len(closest_P - P);
/* Radius of circle on area light that actually affects the shading point. */
const float radius = t / tan_spread;
const float radius = t / cot_half_spread;
/* Local uv coordinates of closest point. */
const float closest_u = dot(axis_u, closest_P - *lightP);
@ -186,7 +186,7 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
float sample_len_u = len_u;
float sample_len_v = len_v;
if (!in_volume_segment && klight->area.tan_spread > 0.0f) {
if (!in_volume_segment && klight->area.cot_half_spread > 0.0f) {
if (!area_light_spread_clamp_area_light(P,
Ng,
&ls->P,
@ -194,7 +194,7 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
&sample_len_u,
axis_v,
&sample_len_v,
klight->area.tan_spread)) {
klight->area.cot_half_spread)) {
return false;
}
}
@ -216,10 +216,10 @@ ccl_device_inline bool area_light_sample(const ccl_global KernelLight *klight,
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
if (klight->area.cot_half_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
}
if (is_round) {
@ -237,10 +237,10 @@ ccl_device_forceinline void area_light_update_position(const ccl_global KernelLi
ls->D = normalize_len(ls->P - P, &ls->t);
ls->pdf = invarea;
if (klight->area.tan_spread > 0.f) {
if (klight->area.cot_half_spread > 0.f) {
ls->eval_fac = 0.25f * invarea;
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
}
}
@ -313,7 +313,7 @@ ccl_device_inline bool area_light_sample_from_intersection(
float sample_len_u = klight->area.len_u;
float sample_len_v = klight->area.len_v;
if (klight->area.tan_spread > 0.0f) {
if (klight->area.cot_half_spread > 0.0f) {
if (!area_light_spread_clamp_area_light(ray_P,
Ng,
&light_P,
@ -321,7 +321,7 @@ ccl_device_inline bool area_light_sample_from_intersection(
&sample_len_u,
axis_v,
&sample_len_v,
klight->area.tan_spread)) {
klight->area.cot_half_spread)) {
return false;
}
}
@ -331,10 +331,10 @@ ccl_device_inline bool area_light_sample_from_intersection(
}
ls->eval_fac = 0.25f * invarea;
if (klight->area.tan_spread > 0.0f) {
if (klight->area.cot_half_spread > 0.0f) {
/* Area Light spread angle attenuation */
ls->eval_fac *= area_light_spread_attenuation(
ls->D, ls->Ng, klight->area.tan_spread, klight->area.normalize_spread);
ls->D, ls->Ng, klight->area.cot_half_spread, klight->area.normalize_spread);
if (ls->eval_fac == 0.0f) {
return false;
}
@ -342,4 +342,46 @@ ccl_device_inline bool area_light_sample_from_intersection(
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool area_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
const float3 N,
const float3 bcone_axis,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (!in_volume_segment) {
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
* worth the overhead to compute the accurate minimal distance? */
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
distance = make_float2(min_distance, min_distance);
}
cos_theta_u = FLT_MAX;
const float3 extentu = klight->area.axis_u * klight->area.len_u;
const float3 extentv = klight->area.axis_v * klight->area.len_v;
for (int i = 0; i < 4; i++) {
const float3 corner = ((i & 1) - 0.5f) * extentu + 0.5f * ((i & 2) - 1) * extentv + centroid;
float distance_point_to_corner;
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
if (!in_volume_segment) {
distance.x = fmaxf(distance.x, distance_point_to_corner);
}
}
const bool front_facing = dot(bcone_axis, point_to_centroid) < 0;
const bool shape_above_surface = dot(N, centroid - P) + fabsf(dot(N, extentu)) +
fabsf(dot(N, extentv)) >
0;
const bool in_volume = is_zero(N);
return (front_facing && shape_above_surface) || in_volume;
}
CCL_NAMESPACE_END

View File

@ -439,4 +439,19 @@ ccl_device float background_light_pdf(KernelGlobals kg, float3 P, float3 directi
return pdf;
}
ccl_device_forceinline bool background_light_tree_parameters(const float3 centroid,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
/* Cover the whole sphere */
cos_theta_u = -1.0f;
distance = make_float2(1.0f, 1.0f);
point_to_centroid = -centroid;
return true;
}
CCL_NAMESPACE_END

View File

@ -107,4 +107,21 @@ ccl_device bool distant_light_sample_from_intersection(KernelGlobals kg,
return true;
}
ccl_device_forceinline bool distant_light_tree_parameters(const float3 centroid,
const float theta_e,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
/* Treating it as a disk light 1 unit away */
cos_theta_u = fast_cosf(theta_e);
distance = make_float2(1.0f / cos_theta_u, 1.0f);
point_to_centroid = -centroid;
return true;
}
CCL_NAMESPACE_END

View File

@ -11,7 +11,7 @@ CCL_NAMESPACE_BEGIN
/* Simple CDF based sampling over all lights in the scene, without taking into
* account shading position or normal. */
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *randu)
ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float &randu)
{
/* This is basically std::upper_bound as used by PBRT, to find a point light or
* triangle to emit from, proportional to area. a good improvement would be to
@ -19,7 +19,7 @@ ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *ra
* arbitrary shaders. */
int first = 0;
int len = kernel_data.integrator.num_distribution + 1;
float r = *randu;
float r = randu;
do {
int half_len = len >> 1;
@ -42,55 +42,32 @@ ccl_device int light_distribution_sample(KernelGlobals kg, ccl_private float *ra
* each area light be stratified as well. */
float distr_min = kernel_data_fetch(light_distribution, index).totarea;
float distr_max = kernel_data_fetch(light_distribution, index + 1).totarea;
*randu = (r - distr_min) / (distr_max - distr_min);
randu = (r - distr_min) / (distr_max - distr_min);
return index;
}
template<bool in_volume_segment>
ccl_device_noinline bool light_distribution_sample(KernelGlobals kg,
float randu,
ccl_private float &randu,
const float randv,
const float time,
const float3 P,
const int bounce,
const uint32_t path_flag,
ccl_private LightSample *ls)
ccl_private int &emitter_object,
ccl_private int &emitter_prim,
ccl_private int &emitter_shader_flag,
ccl_private float &emitter_pdf_selection)
{
/* Sample light index from distribution. */
const int index = light_distribution_sample(kg, &randu);
const int index = light_distribution_sample(kg, randu);
ccl_global const KernelLightDistribution *kdistribution = &kernel_data_fetch(light_distribution,
index);
const int prim = kdistribution->prim;
if (prim >= 0) {
/* Mesh light. */
const int object = kdistribution->mesh_light.object_id;
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
const int shader_flag = kdistribution->mesh_light.shader_flag;
triangle_light_sample<in_volume_segment>(kg, prim, object, randu, randv, time, ls, P);
ls->shader |= shader_flag;
return (ls->pdf > 0.0f);
}
const int lamp = -prim - 1;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
if (!light_sample<in_volume_segment>(kg, lamp, randu, randv, P, path_flag, ls)) {
return false;
}
ls->pdf_selection = kernel_data.integrator.distribution_pdf_lights;
ls->pdf *= ls->pdf_selection;
emitter_object = kdistribution->mesh_light.object_id;
emitter_prim = kdistribution->prim;
emitter_shader_flag = kdistribution->mesh_light.shader_flag;
emitter_pdf_selection = kernel_data.integrator.distribution_pdf_lights;
return true;
}

View File

@ -108,4 +108,29 @@ ccl_device_inline bool point_light_sample_from_intersection(
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool point_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (in_volume_segment) {
cos_theta_u = 1.0f; /* Any value in [-1, 1], irrelevant since theta = 0 */
return true;
}
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
const float radius = klight->spot.radius;
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
cos_theta_u = min_distance / hypotenus;
distance = make_float2(hypotenus, min_distance);
return true;
}
CCL_NAMESPACE_END

View File

@ -8,6 +8,7 @@
#include "kernel/light/distribution.h"
#include "kernel/light/light.h"
#include "kernel/light/tree.h"
#include "kernel/sample/mapping.h"
#include "kernel/sample/mis.h"
@ -315,11 +316,13 @@ ccl_device_inline float light_sample_mis_weight_nee(KernelGlobals kg,
/* Next event estimation sampling.
*
* Sample a position on a light in the scene, from a position on a surface or
* from a volume segment. */
* from a volume segment.
*
* Uses either a flat distribution or light tree. */
ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
float randu,
const float randv,
float randv,
const float time,
const float3 P,
const float3 D,
@ -328,13 +331,89 @@ ccl_device_inline bool light_sample_from_volume_segment(KernelGlobals kg,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<true>(kg, randu, randv, time, P, bounce, path_flag, ls);
/* Select an emitter. */
int emitter_object = 0;
int emitter_prim = 0;
int emitter_shader_flag = 0;
float emitter_pdf_selection = 0.0f;
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
if (!light_tree_sample<true>(kg,
randu,
randv,
time,
P,
D,
t,
SD_BSDF_HAS_TRANSMISSION,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
else
#endif
{
if (!light_distribution_sample(kg,
randu,
randv,
time,
P,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
/* Set first, triangle light sampling from flat distribution will override. */
ls->pdf_selection = emitter_pdf_selection;
/* Sample a point on the chosen emitter. */
if (emitter_prim >= 0) {
/* Mesh light. */
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
if (!triangle_light_sample<true>(
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
return false;
}
}
else {
/* Light object. */
const int lamp = ~emitter_prim;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
if (!light_sample<true>(kg, lamp, randu, randv, P, path_flag, ls)) {
return false;
}
}
ls->pdf *= ls->pdf_selection;
ls->shader |= emitter_shader_flag;
return (ls->pdf > 0);
}
ccl_device bool light_sample_from_position(KernelGlobals kg,
ccl_private const RNGState *rng_state,
const float randu,
const float randv,
float randu,
float randv,
const float time,
const float3 P,
const float3 N,
@ -343,7 +422,84 @@ ccl_device bool light_sample_from_position(KernelGlobals kg,
const uint32_t path_flag,
ccl_private LightSample *ls)
{
return light_distribution_sample<false>(kg, randu, randv, time, P, bounce, path_flag, ls);
/* Select an emitter. */
int emitter_object = 0;
int emitter_prim = 0;
int emitter_shader_flag = 0;
float emitter_pdf_selection = 0.0f;
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
if (!light_tree_sample<false>(kg,
randu,
randv,
time,
P,
N,
0,
shader_flags,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
else
#endif
{
if (!light_distribution_sample(kg,
randu,
randv,
time,
P,
bounce,
path_flag,
emitter_object,
emitter_prim,
emitter_shader_flag,
emitter_pdf_selection)) {
return false;
}
}
/* Set first, triangle light sampling from flat distribution will override. */
ls->pdf_selection = emitter_pdf_selection;
/* Sample a point on the chosen emitter.
* TODO: deduplicate code with light_sample_from_volume_segment? */
if (emitter_prim >= 0) {
/* Mesh light. */
/* Exclude synthetic meshes from shadow catcher pass. */
if ((path_flag & PATH_RAY_SHADOW_CATCHER_PASS) &&
!(kernel_data_fetch(object_flag, emitter_object) & SD_OBJECT_SHADOW_CATCHER)) {
return false;
}
if (!triangle_light_sample<false>(
kg, emitter_prim, emitter_object, randu, randv, time, ls, P)) {
return false;
}
}
else {
/* Light object. */
const int lamp = ~emitter_prim;
if (UNLIKELY(light_select_reached_max_bounces(kg, lamp, bounce))) {
return false;
}
if (!light_sample<false>(kg, lamp, randu, randv, P, path_flag, ls)) {
return false;
}
}
ls->pdf *= ls->pdf_selection;
ls->shader |= emitter_shader_flag;
return (ls->pdf > 0);
}
ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
@ -358,6 +514,16 @@ ccl_device_inline bool light_sample_new_position(KernelGlobals kg,
if (!triangle_light_sample<false>(kg, ls->prim, ls->object, randu, randv, time, ls, P)) {
return false;
}
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
ls->pdf *= ls->pdf_selection;
}
else
#endif
{
/* Handled in triangle_light_sample for effeciency. */
}
return true;
}
else {
@ -401,7 +567,19 @@ ccl_device_inline float light_sample_mis_weight_forward_surface(KernelGlobals kg
float pdf = triangle_light_pdf(kg, sd, t);
/* Light selection pdf. */
/* Handled in triangle_light_pdf for effeciency. */
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
uint lookup_offset = kernel_data_fetch(object_lookup_offset, sd->object);
uint prim_offset = kernel_data_fetch(object_prim_offset, sd->object);
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, sd->prim - prim_offset + lookup_offset);
}
else
#endif
{
/* Handled in triangle_light_pdf for efficiency. */
}
return light_sample_mis_weight_forward(kg, bsdf_pdf, pdf);
}
@ -416,7 +594,16 @@ ccl_device_inline float light_sample_mis_weight_forward_lamp(KernelGlobals kg,
float pdf = ls->pdf;
/* Light selection pdf. */
pdf *= light_distribution_pdf_lamp(kg);
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
pdf *= light_tree_pdf(kg, P, N, path_flag, ~ls->lamp);
}
else
#endif
{
pdf *= light_distribution_pdf_lamp(kg);
}
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
}
@ -441,7 +628,16 @@ ccl_device_inline float light_sample_mis_weight_forward_background(KernelGlobals
float pdf = background_light_pdf(kg, ray_P, ray_D);
/* Light selection pdf. */
pdf *= light_distribution_pdf_lamp(kg);
#ifdef __LIGHT_TREE__
if (kernel_data.integrator.use_light_tree) {
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
pdf *= light_tree_pdf(kg, ray_P, N, path_flag, ~kernel_data.background.light_index);
}
else
#endif
{
pdf *= light_distribution_pdf_lamp(kg);
}
return light_sample_mis_weight_forward(kg, mis_ray_pdf, pdf);
}

View File

@ -150,4 +150,30 @@ ccl_device_inline bool spot_light_sample_from_intersection(
return true;
}
template<bool in_volume_segment>
ccl_device_forceinline bool spot_light_tree_parameters(const ccl_global KernelLight *klight,
const float3 centroid,
const float3 P,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
float min_distance;
const float3 point_to_centroid_ = safe_normalize_len(centroid - P, &min_distance);
const float radius = klight->spot.radius;
const float hypotenus = sqrtf(sqr(radius) + sqr(min_distance));
cos_theta_u = min_distance / hypotenus;
if (in_volume_segment) {
return true;
}
distance = make_float2(hypotenus, min_distance);
point_to_centroid = point_to_centroid_;
return true;
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,691 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#pragma once
#include "kernel/light/area.h"
#include "kernel/light/common.h"
#include "kernel/light/light.h"
#include "kernel/light/spot.h"
#include "kernel/light/triangle.h"
CCL_NAMESPACE_BEGIN
/* TODO: this seems like a relative expensive computation, and we can make it a lot cheaper
* by using a bounding sphere instead of a bounding box. This will be more inaccurate, but it
* might be fine when used along with the adaptive splitting. */
ccl_device float light_tree_cos_bounding_box_angle(const BoundingBox bbox,
const float3 P,
const float3 point_to_centroid)
{
if (P.x > bbox.min.x && P.y > bbox.min.y && P.z > bbox.min.z && P.x < bbox.max.x &&
P.y < bbox.max.y && P.z < bbox.max.z) {
/* If P is inside the bbox, `theta_u` covers the whole sphere */
return -1.0f;
}
float cos_theta_u = 1.0f;
/* Iterate through all 8 possible points of the bounding box. */
for (int i = 0; i < 8; ++i) {
const float3 corner = make_float3((i & 1) ? bbox.max.x : bbox.min.x,
(i & 2) ? bbox.max.y : bbox.min.y,
(i & 4) ? bbox.max.z : bbox.min.z);
/* Caculate the bounding box angle. */
float3 point_to_corner = normalize(corner - P);
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
}
return cos_theta_u;
}
ccl_device_forceinline float sin_from_cos(const float c)
{
return safe_sqrtf(1.0f - sqr(c));
}
/* Compute vector v as in Fig .8. P_v is the corresponding point along the ray ccl_device float3 */
ccl_device float3 compute_v(
const float3 centroid, const float3 P, const float3 D, const float3 bcone_axis, const float t)
{
const float3 unnormalized_v0 = P - centroid;
float len_v0;
const float3 unnormalized_v1 = unnormalized_v0 + D * fminf(t, 1e12f);
const float3 v0 = normalize_len(unnormalized_v0, &len_v0);
const float3 v1 = normalize(unnormalized_v1);
const float3 o0 = v0;
float3 o1, o2;
make_orthonormals_tangent(o0, v1, &o1, &o2);
const float dot_o0_a = dot(o0, bcone_axis);
const float dot_o1_a = dot(o1, bcone_axis);
const float cos_phi0 = dot_o0_a / sqrtf(sqr(dot_o0_a) + sqr(dot_o1_a));
return (dot_o1_a < 0 || dot(v0, v1) > cos_phi0) ? (dot_o0_a > dot(v1, bcone_axis) ? v0 : v1) :
cos_phi0 * o0 + sin_from_cos(cos_phi0) * o1;
}
/* This is the general function for calculating the importance of either a cluster or an emitter.
* Both of the specialized functions obtain the necessary data before calling this function. */
template<bool in_volume_segment>
ccl_device void light_tree_importance(const float3 N_or_D,
const bool has_transmission,
const float3 point_to_centroid,
const float cos_theta_u,
const BoundingCone bcone,
const float max_distance,
const float min_distance,
const float t,
const float energy,
ccl_private float &max_importance,
ccl_private float &min_importance)
{
max_importance = 0.0f;
min_importance = 0.0f;
const float sin_theta_u = sin_from_cos(cos_theta_u);
/* cos(theta_i') in the paper, omitted for volume */
float cos_min_incidence_angle = 1.0f;
float cos_max_incidence_angle = 1.0f;
/* when sampling the light tree for the second time in `shade_volume.h` and when query the pdf in
* `sample.h` */
const bool in_volume = is_zero(N_or_D);
if (!in_volume_segment && !in_volume) {
const float3 N = N_or_D;
const float cos_theta_i = has_transmission ? fabsf(dot(point_to_centroid, N)) :
dot(point_to_centroid, N);
const float sin_theta_i = sin_from_cos(cos_theta_i);
/* cos_min_incidence_angle = cos(max{theta_i - theta_u, 0}) = cos(theta_i') in the paper */
cos_min_incidence_angle = cos_theta_i >= cos_theta_u ?
1.0f :
cos_theta_i * cos_theta_u + sin_theta_i * sin_theta_u;
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
* surface. This is more accurate than the bbox test if we are calculating the importance of
* an emitter with radius */
if (!has_transmission && cos_min_incidence_angle < 0) {
return;
}
/* cos_max_incidence_angle = cos(min{theta_i + theta_u, pi}) */
cos_max_incidence_angle = fmaxf(cos_theta_i * cos_theta_u - sin_theta_i * sin_theta_u, 0.0f);
}
/* cos(theta - theta_u) */
const float cos_theta = dot(bcone.axis, -point_to_centroid);
const float sin_theta = sin_from_cos(cos_theta);
const float cos_theta_minus_theta_u = cos_theta * cos_theta_u + sin_theta * sin_theta_u;
float cos_theta_o, sin_theta_o;
fast_sincosf(bcone.theta_o, &sin_theta_o, &cos_theta_o);
/* minimum angle an emitters axis would form with the direction to the shading point,
* cos(theta') in the paper */
float cos_min_outgoing_angle;
if ((cos_theta >= cos_theta_u) || (cos_theta_minus_theta_u >= cos_theta_o)) {
/* theta - theta_o - theta_u <= 0 */
kernel_assert((fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u)) < 5e-4f);
cos_min_outgoing_angle = 1.0f;
}
else if ((bcone.theta_o + bcone.theta_e > M_PI_F) ||
(cos_theta_minus_theta_u > cos(bcone.theta_o + bcone.theta_e))) {
/* theta' = theta - theta_o - theta_u < theta_e */
kernel_assert(
(fast_acosf(cos_theta) - bcone.theta_o - fast_acosf(cos_theta_u) - bcone.theta_e) < 5e-4f);
const float sin_theta_minus_theta_u = sin_from_cos(cos_theta_minus_theta_u);
cos_min_outgoing_angle = cos_theta_minus_theta_u * cos_theta_o +
sin_theta_minus_theta_u * sin_theta_o;
}
else {
/* cluster invisible */
return;
}
/* TODO: find a good approximation for f_a. */
const float f_a = 1.0f;
/* TODO: also consider t (or theta_a, theta_b) for volume */
max_importance = fabsf(f_a * cos_min_incidence_angle * energy * cos_min_outgoing_angle /
(in_volume_segment ? min_distance : sqr(min_distance)));
/* TODO: also min importance for volume? */
if (in_volume_segment) {
min_importance = max_importance;
return;
}
/* cos(theta + theta_o + theta_u) if theta + theta_o + theta_u < theta_e, 0 otherwise */
float cos_max_outgoing_angle;
const float cos_theta_plus_theta_u = cos_theta * cos_theta_u - sin_theta * sin_theta_u;
if (bcone.theta_e - bcone.theta_o < 0 || cos_theta < 0 || cos_theta_u < 0 ||
cos_theta_plus_theta_u < cos(bcone.theta_e - bcone.theta_o)) {
min_importance = 0.0f;
}
else {
const float sin_theta_plus_theta_u = sin_from_cos(cos_theta_plus_theta_u);
cos_max_outgoing_angle = cos_theta_plus_theta_u * cos_theta_o -
sin_theta_plus_theta_u * sin_theta_o;
min_importance = fabsf(f_a * cos_max_incidence_angle * energy * cos_max_outgoing_angle /
sqr(max_distance));
}
}
template<bool in_volume_segment>
ccl_device bool compute_emitter_centroid_and_dir(KernelGlobals kg,
ccl_global const KernelLightTreeEmitter *kemitter,
const float3 P,
ccl_private float3 &centroid,
ccl_private packed_float3 &dir)
{
const int prim_id = kemitter->prim_id;
if (prim_id < 0) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
centroid = klight->co;
switch (klight->type) {
case LIGHT_SPOT:
dir = klight->spot.dir;
break;
case LIGHT_POINT:
/* Disk-oriented normal */
dir = safe_normalize(P - centroid);
break;
case LIGHT_AREA:
dir = klight->area.dir;
break;
case LIGHT_BACKGROUND:
/* Aarbitrary centroid and direction */
centroid = make_float3(0.0f, 0.0f, 1.0f);
dir = make_float3(0.0f, 0.0f, -1.0f);
return !in_volume_segment;
case LIGHT_DISTANT:
dir = centroid;
return !in_volume_segment;
default:
return false;
}
}
else {
const int object = kemitter->mesh_light.object_id;
float3 vertices[3];
triangle_world_space_vertices(kg, object, prim_id, -1.0f, vertices);
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_FRONT) {
dir = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
}
else if (kemitter->mesh_light.emission_sampling == EMISSION_SAMPLING_BACK) {
dir = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
}
else {
/* Double sided: any vector in the plane. */
dir = safe_normalize(vertices[0] - vertices[1]);
}
}
return true;
}
template<bool in_volume_segment>
ccl_device void light_tree_emitter_importance(KernelGlobals kg,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
int emitter_index,
ccl_private float &max_importance,
ccl_private float &min_importance)
{
const ccl_global KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
emitter_index);
max_importance = 0.0f;
min_importance = 0.0f;
BoundingCone bcone;
bcone.theta_o = kemitter->theta_o;
bcone.theta_e = kemitter->theta_e;
float cos_theta_u;
float2 distance; /* distance.x = max_distance, distance.y = mix_distance */
float3 centroid, point_to_centroid, P_c;
if (!compute_emitter_centroid_and_dir<in_volume_segment>(
kg, kemitter, P, centroid, bcone.axis)) {
return;
}
const int prim_id = kemitter->prim_id;
if (in_volume_segment) {
const float3 D = N_or_D;
/* Closest point */
P_c = P + dot(centroid - P, D) * D;
/* minimal distance of the ray to the cluster */
distance.x = len(centroid - P_c);
distance.y = distance.x;
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
}
else {
P_c = P;
}
bool is_visible;
if (prim_id < 0) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, ~prim_id);
switch (klight->type) {
/* Function templates only modifies cos_theta_u when in_volume_segment = true */
case LIGHT_SPOT:
is_visible = spot_light_tree_parameters<in_volume_segment>(
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
break;
case LIGHT_POINT:
is_visible = point_light_tree_parameters<in_volume_segment>(
klight, centroid, P_c, cos_theta_u, distance, point_to_centroid);
bcone.theta_o = 0.0f;
break;
case LIGHT_AREA:
is_visible = area_light_tree_parameters<in_volume_segment>(
klight, centroid, P_c, N_or_D, bcone.axis, cos_theta_u, distance, point_to_centroid);
break;
case LIGHT_BACKGROUND:
is_visible = background_light_tree_parameters(
centroid, cos_theta_u, distance, point_to_centroid);
break;
case LIGHT_DISTANT:
is_visible = distant_light_tree_parameters(
centroid, bcone.theta_e, cos_theta_u, distance, point_to_centroid);
break;
default:
return;
}
}
else { /* mesh light */
is_visible = triangle_light_tree_parameters<in_volume_segment>(
kg, kemitter, centroid, P_c, N_or_D, bcone, cos_theta_u, distance, point_to_centroid);
}
is_visible |= has_transmission;
if (!is_visible) {
return;
}
light_tree_importance<in_volume_segment>(N_or_D,
has_transmission,
point_to_centroid,
cos_theta_u,
bcone,
distance.x,
distance.y,
t,
kemitter->energy,
max_importance,
min_importance);
}
template<bool in_volume_segment>
ccl_device void light_tree_node_importance(KernelGlobals kg,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
const ccl_global KernelLightTreeNode *knode,
ccl_private float &max_importance,
ccl_private float &min_importance)
{
max_importance = 0.0f;
min_importance = 0.0f;
if (knode->num_prims == 1) {
/* At a leaf node with only one emitter */
light_tree_emitter_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, -knode->child_index, max_importance, min_importance);
}
else if (knode->num_prims != 0) {
const BoundingCone bcone = knode->bcone;
const BoundingBox bbox = knode->bbox;
float3 point_to_centroid;
float cos_theta_u;
float distance;
if (knode->bit_trail == 1) {
/* distant light node */
if (in_volume_segment) {
return;
}
point_to_centroid = -bcone.axis;
cos_theta_u = fast_cosf(bcone.theta_o);
distance = 1.0f;
}
else {
const float3 centroid = 0.5f * (bbox.min + bbox.max);
if (in_volume_segment) {
const float3 D = N_or_D;
const float3 closest_point = P + dot(centroid - P, D) * D;
/* minimal distance of the ray to the cluster */
distance = len(centroid - closest_point);
point_to_centroid = -compute_v(centroid, P, D, bcone.axis, t);
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, closest_point, point_to_centroid);
}
else {
const float3 N = N_or_D;
const float3 bbox_extent = bbox.max - centroid;
const bool bbox_is_visible = has_transmission |
(dot(N, centroid - P) + dot(fabs(N), fabs(bbox_extent)) > 0);
/* If the node is guaranteed to be behind the surface we're sampling, and the surface is
* opaque, then we can give the node an importance of 0 as it contributes nothing to the
* surface. */
if (!bbox_is_visible) {
return;
}
point_to_centroid = normalize_len(centroid - P, &distance);
cos_theta_u = light_tree_cos_bounding_box_angle(bbox, P, point_to_centroid);
}
/* clamp distance to half the radius of the cluster when splitting is disabled */
distance = fmaxf(0.5f * len(centroid - bbox.max), distance);
}
/* TODO: currently max_distance = min_distance, max_importance = min_importance for the
* nodes. Do we need better weights for complex scenes? */
light_tree_importance<in_volume_segment>(N_or_D,
has_transmission,
point_to_centroid,
cos_theta_u,
bcone,
distance,
distance,
t,
knode->energy,
max_importance,
min_importance);
}
}
ccl_device void sample_resevoir(const int current_index,
const float current_weight,
ccl_private int &selected_index,
ccl_private float &selected_weight,
ccl_private float &total_weight,
ccl_private float &rand)
{
if (current_weight == 0.0f) {
return;
}
total_weight += current_weight;
float thresh = current_weight / total_weight;
if (rand <= thresh) {
selected_index = current_index;
selected_weight = current_weight;
rand = rand / thresh;
}
else {
rand = (rand - thresh) / (1.0f - thresh);
}
kernel_assert(rand >= 0.0f && rand <= 1.0f);
return;
}
/* pick an emitter from a leaf node using resevoir sampling, keep two reservoirs for upper and
* lower bounds */
template<bool in_volume_segment>
ccl_device int light_tree_cluster_select_emitter(KernelGlobals kg,
ccl_private float &rand,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
const ccl_global KernelLightTreeNode *knode,
ccl_private float *pdf_factor)
{
float selected_importance[2] = {0.0f, 0.0f};
float total_importance[2] = {0.0f, 0.0f};
int selected_index = -1;
/* Mark emitters with zero importance. Used for resevoir when total minimum importance = 0 */
kernel_assert(knode->num_prims <= sizeof(uint) * 8);
uint has_importance = 0;
const bool sample_max = (rand > 0.5f); /* sampling using the maximum importance */
rand = rand * 2.0f - float(sample_max);
for (int i = 0; i < knode->num_prims; i++) {
int current_index = -knode->child_index + i;
/* maximum importance = importance[0], mininum importance = importance[1] */
float importance[2];
light_tree_emitter_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, current_index, importance[0], importance[1]);
sample_resevoir(current_index,
importance[!sample_max],
selected_index,
selected_importance[!sample_max],
total_importance[!sample_max],
rand);
if (selected_index == current_index) {
selected_importance[sample_max] = importance[sample_max];
}
total_importance[sample_max] += importance[sample_max];
has_importance |= ((importance[0] > 0) << i);
}
if (total_importance[0] == 0.0f) {
return -1;
}
if (total_importance[1] == 0.0f) {
/* uniformly sample emitters with positive maximum importance */
if (sample_max) {
selected_importance[1] = 1.0f;
total_importance[1] = float(popcount(has_importance));
}
else {
selected_index = -1;
for (int i = 0; i < knode->num_prims; i++) {
int current_index = -knode->child_index + i;
sample_resevoir(current_index,
float(has_importance & 1),
selected_index,
selected_importance[1],
total_importance[1],
rand);
has_importance >>= 1;
}
float discard;
light_tree_emitter_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, selected_index, selected_importance[0], discard);
}
}
*pdf_factor = 0.5f * (selected_importance[0] / total_importance[0] +
selected_importance[1] / total_importance[1]);
return selected_index;
}
template<bool in_volume_segment>
ccl_device bool get_left_probability(KernelGlobals kg,
const float3 P,
const float3 N_or_D,
const float t,
const bool has_transmission,
const int left_index,
const int right_index,
ccl_private float &left_probability)
{
const ccl_global KernelLightTreeNode *left = &kernel_data_fetch(light_tree_nodes, left_index);
const ccl_global KernelLightTreeNode *right = &kernel_data_fetch(light_tree_nodes, right_index);
float min_left_importance, max_left_importance, min_right_importance, max_right_importance;
light_tree_node_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, left, max_left_importance, min_left_importance);
light_tree_node_importance<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, right, max_right_importance, min_right_importance);
const float total_max_importance = max_left_importance + max_right_importance;
if (total_max_importance == 0.0f) {
return false;
}
const float total_min_importance = min_left_importance + min_right_importance;
/* average two probabilities of picking the left child node using lower and upper bounds */
const float probability_max = max_left_importance / total_max_importance;
const float probability_min = total_min_importance > 0 ?
min_left_importance / total_min_importance :
0.5f * (float(max_left_importance > 0) +
float(max_right_importance == 0.0f));
left_probability = 0.5f * (probability_max + probability_min);
return true;
}
template<bool in_volume_segment>
ccl_device_noinline bool light_tree_sample(KernelGlobals kg,
ccl_private float &randu,
ccl_private float &randv,
const float time,
const float3 P,
const float3 N_or_D,
const float t,
const int shader_flags,
const int bounce,
const uint32_t path_flag,
ccl_private int &emitter_object,
ccl_private int &emitter_prim,
ccl_private int &emitter_shader_flag,
ccl_private float &emitter_pdf_selection)
{
if (!kernel_data.integrator.use_direct_light) {
return false;
}
const bool has_transmission = (shader_flags & SD_BSDF_HAS_TRANSMISSION);
float pdf_leaf = 1.0f;
float pdf_emitter_from_leaf = 1.0f;
int selected_light = -1;
int node_index = 0; /* root node */
/* Traverse the light tree until a leaf node is reached. */
while (true) {
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
if (knode->child_index <= 0) {
/* At a leaf node, we pick an emitter */
selected_light = light_tree_cluster_select_emitter<in_volume_segment>(
kg, randv, P, N_or_D, t, has_transmission, knode, &pdf_emitter_from_leaf);
break;
}
/* At an interior node, the left child is directly after the parent,
* while the right child is stored as the child index. */
const int left_index = node_index + 1;
const int right_index = knode->child_index;
float left_prob;
if (!get_left_probability<in_volume_segment>(
kg, P, N_or_D, t, has_transmission, left_index, right_index, left_prob)) {
return false; /* both child nodes have zero importance */
}
float discard;
float total_prob = left_prob;
node_index = left_index;
sample_resevoir(right_index, 1.0f - left_prob, node_index, discard, total_prob, randu);
pdf_leaf *= (node_index == left_index) ? left_prob : (1.0f - left_prob);
}
if (selected_light < 0) {
return false;
}
/* Return info about chosen emitter. */
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
selected_light);
emitter_object = kemitter->mesh_light.object_id;
emitter_prim = kemitter->prim_id;
emitter_shader_flag = kemitter->mesh_light.shader_flag;
emitter_pdf_selection = pdf_leaf * pdf_emitter_from_leaf;
return true;
}
/* We need to be able to find the probability of selecting a given light for MIS. */
ccl_device float light_tree_pdf(
KernelGlobals kg, const float3 P, const float3 N, const int path_flag, const int prim)
{
const bool has_transmission = (path_flag & PATH_RAY_MIS_HAD_TRANSMISSION);
/* Target emitter info */
const int target_emitter = (prim >= 0) ? kernel_data_fetch(triangle_to_tree, prim) :
kernel_data_fetch(light_to_tree, ~prim);
ccl_global const KernelLightTreeEmitter *kemitter = &kernel_data_fetch(light_tree_emitters,
target_emitter);
const int target_leaf = kemitter->parent_index;
ccl_global const KernelLightTreeNode *kleaf = &kernel_data_fetch(light_tree_nodes, target_leaf);
uint bit_trail = kleaf->bit_trail;
int node_index = 0; /* root node */
float pdf = 1.0f;
/* Traverse the light tree until we reach the target leaf node */
while (true) {
const ccl_global KernelLightTreeNode *knode = &kernel_data_fetch(light_tree_nodes, node_index);
if (knode->child_index <= 0) {
break;
}
/* Interior node */
const int left_index = node_index + 1;
const int right_index = knode->child_index;
float left_prob;
if (!get_left_probability<false>(
kg, P, N, 0, has_transmission, left_index, right_index, left_prob)) {
return 0.0f;
}
const bool go_left = (bit_trail & 1) == 0;
bit_trail >>= 1;
pdf *= go_left ? left_prob : (1.0f - left_prob);
node_index = go_left ? left_index : right_index;
if (pdf == 0) {
return 0.0f;
}
}
kernel_assert(node_index == target_leaf);
/* Iterate through leaf node to find the probability of sampling the target emitter. */
float target_max_importance = 0.0f;
float target_min_importance = 0.0f;
float total_max_importance = 0.0f;
float total_min_importance = 0.0f;
int num_has_importance = 0;
for (int i = 0; i < kleaf->num_prims; i++) {
const int emitter = -kleaf->child_index + i;
float max_importance, min_importance;
light_tree_emitter_importance<false>(
kg, P, N, 0, has_transmission, emitter, max_importance, min_importance);
num_has_importance += (max_importance > 0);
if (emitter == target_emitter) {
target_max_importance = max_importance;
target_min_importance = min_importance;
}
total_max_importance += max_importance;
total_min_importance += min_importance;
}
if (target_max_importance > 0.0f) {
return pdf * 0.5f *
(target_max_importance / total_max_importance +
(total_min_importance > 0 ? target_min_importance / total_min_importance :
1.0f / num_has_importance));
}
return 0.0f;
}
CCL_NAMESPACE_END

View File

@ -103,16 +103,18 @@ ccl_device_forceinline float triangle_light_pdf(KernelGlobals kg,
}
/* Belongs in distribution.h but can reuse computations here. */
float distribution_area = area;
if (!kernel_data.integrator.use_light_tree) {
float distribution_area = area;
if (has_motion && area != 0.0f) {
/* For motion blur need area of triangle at fixed time as used in the CDF. */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
distribution_area = triangle_area(V[0], V[1], V[2]);
if (has_motion && area != 0.0f) {
/* For motion blur need area of triangle at fixed time as used in the CDF. */
triangle_world_space_vertices(kg, sd->object, sd->prim, -1.0f, V);
distribution_area = triangle_area(V[0], V[1], V[2]);
}
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
}
pdf *= distribution_area * kernel_data.integrator.distribution_pdf_triangles;
return pdf;
}
@ -265,17 +267,63 @@ ccl_device_forceinline bool triangle_light_sample(KernelGlobals kg,
}
/* Belongs in distribution.h but can reuse computations here. */
float distribution_area = area;
if (!kernel_data.integrator.use_light_tree) {
float distribution_area = area;
if (has_motion && area != 0.0f) {
/* For motion blur need area of triangle at fixed time as used in the CDF. */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
distribution_area = triangle_area(V[0], V[1], V[2]);
if (has_motion && area != 0.0f) {
/* For motion blur need area of triangle at fixed time as used in the CDF. */
triangle_world_space_vertices(kg, object, prim, -1.0f, V);
distribution_area = triangle_area(V[0], V[1], V[2]);
}
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
}
ls->pdf_selection = distribution_area * kernel_data.integrator.distribution_pdf_triangles;
ls->pdf *= ls->pdf_selection;
return (ls->pdf > 0.0f);
}
template<bool in_volume_segment>
ccl_device_forceinline bool triangle_light_tree_parameters(
KernelGlobals kg,
const ccl_global KernelLightTreeEmitter *kemitter,
const float3 centroid,
const float3 P,
const float3 N,
const BoundingCone bcone,
ccl_private float &cos_theta_u,
ccl_private float2 &distance,
ccl_private float3 &point_to_centroid)
{
if (!in_volume_segment) {
/* TODO: a cheap substitute for minimal distance between point and primitive. Does it
* worth the overhead to compute the accurate minimal distance? */
float min_distance;
point_to_centroid = safe_normalize_len(centroid - P, &min_distance);
distance = make_float2(min_distance, min_distance);
}
cos_theta_u = FLT_MAX;
const int object = kemitter->mesh_light.object_id;
float3 vertices[3];
triangle_world_space_vertices(kg, object, kemitter->prim_id, -1.0f, vertices);
bool shape_above_surface = false;
for (int i = 0; i < 3; i++) {
const float3 corner = vertices[i];
float distance_point_to_corner;
const float3 point_to_corner = safe_normalize_len(corner - P, &distance_point_to_corner);
cos_theta_u = fminf(cos_theta_u, dot(point_to_centroid, point_to_corner));
shape_above_surface |= dot(point_to_corner, N) > 0;
if (!in_volume_segment) {
distance.x = fmaxf(distance.x, distance_point_to_corner);
}
}
const bool front_facing = bcone.theta_o != 0.0f || dot(bcone.axis, point_to_centroid) < 0;
const bool in_volume = is_zero(N);
return (front_facing && shape_above_surface) || in_volume;
}
CCL_NAMESPACE_END

View File

@ -60,6 +60,7 @@ CCL_NAMESPACE_BEGIN
#define __DENOISING_FEATURES__
#define __DPDU__
#define __HAIR__
#define __LIGHT_TREE__
#define __OBJECT_MOTION__
#define __PASSES__
#define __PATCH_EVAL__
@ -74,6 +75,11 @@ CCL_NAMESPACE_BEGIN
#define __VISIBILITY_FLAG__
#define __VOLUME__
/* TODO: solve internal compiler errors and enable light tree on HIP. */
#ifdef __KERNEL_HIP__
# undef __LIGHT_TREE__
#endif
/* Device specific features */
#ifdef WITH_OSL
# define __OSL__
@ -226,7 +232,7 @@ enum PathRayFlag : uint32_t {
*/
/* Surface had transmission component at previous bounce. Used for light tree
* traversal and culling to be consistent with MIS pdf at the next bounce. */
* traversal and culling to be consistent with MIS PDF at the next bounce. */
PATH_RAY_MIS_HAD_TRANSMISSION = (1U << 10U),
/* Don't apply multiple importance sampling weights to emission from
@ -348,7 +354,6 @@ typedef enum PassType {
PASS_EMISSION,
PASS_BACKGROUND,
PASS_AO,
PASS_SHADOW,
PASS_DIFFUSE,
PASS_DIFFUSE_DIRECT,
PASS_DIFFUSE_INDIRECT,
@ -1302,7 +1307,7 @@ typedef struct KernelAreaLight {
float len_v;
packed_float3 dir;
float invarea;
float tan_spread;
float cot_half_spread;
float normalize_spread;
float pad[2];
} KernelAreaLight;
@ -1336,19 +1341,70 @@ static_assert_align(KernelLight, 16);
typedef struct KernelLightDistribution {
float totarea;
int prim;
union {
struct {
int shader_flag;
int object_id;
} mesh_light;
struct {
float pad;
float size;
} lamp;
};
struct {
int shader_flag;
int object_id;
} mesh_light;
} KernelLightDistribution;
static_assert_align(KernelLightDistribution, 16);
/* Bounding box. */
using BoundingBox = struct BoundingBox {
packed_float3 min;
packed_float3 max;
};
using BoundingCone = struct BoundingCone {
packed_float3 axis;
float theta_o;
float theta_e;
};
typedef struct KernelLightTreeNode {
/* Bounding box. */
BoundingBox bbox;
/* Bounding cone. */
BoundingCone bcone;
/* Energy. */
float energy;
/* If this is 0 or less, we're at a leaf node
* and the negative value indexes into the first child of the light array.
* Otherwise, it's an index to the node's second child. */
int child_index;
int num_prims; /* leaf nodes need to know the number of primitives stored. */
/* Bit trail. */
uint bit_trail;
/* Padding. */
int pad;
} KernelLightTreeNode;
static_assert_align(KernelLightTreeNode, 16);
typedef struct KernelLightTreeEmitter {
/* Bounding cone. */
float theta_o;
float theta_e;
/* Energy. */
float energy;
/* prim_id denotes the location in the lights or triangles array. */
int prim_id;
struct {
int shader_flag;
int object_id;
EmissionSampling emission_sampling;
} mesh_light;
/* Parent. */
int parent_index;
} KernelLightTreeEmitter;
static_assert_align(KernelLightTreeEmitter, 16);
typedef struct KernelParticle {
int index;
float age;
@ -1548,22 +1604,19 @@ enum KernelFeatureFlag : uint32_t {
/* Light render passes. */
KERNEL_FEATURE_LIGHT_PASSES = (1U << 21U),
/* Shadow render pass. */
KERNEL_FEATURE_SHADOW_PASS = (1U << 22U),
/* AO. */
KERNEL_FEATURE_AO_PASS = (1U << 23U),
KERNEL_FEATURE_AO_ADDITIVE = (1U << 24U),
KERNEL_FEATURE_AO_PASS = (1U << 22U),
KERNEL_FEATURE_AO_ADDITIVE = (1U << 23U),
KERNEL_FEATURE_AO = (KERNEL_FEATURE_AO_PASS | KERNEL_FEATURE_AO_ADDITIVE),
/* MNEE. */
KERNEL_FEATURE_MNEE = (1U << 25U),
KERNEL_FEATURE_MNEE = (1U << 24U),
/* Path guiding. */
KERNEL_FEATURE_PATH_GUIDING = (1U << 26U),
KERNEL_FEATURE_PATH_GUIDING = (1U << 25U),
/* OSL. */
KERNEL_FEATURE_OSL = (1U << 27U),
KERNEL_FEATURE_OSL = (1U << 26U),
};
/* Shader node feature mask, to specialize shader evaluation for kernels. */

View File

@ -25,6 +25,7 @@ set(SRC
integrator.cpp
jitter.cpp
light.cpp
light_tree.cpp
mesh.cpp
mesh_displace.cpp
mesh_subdivision.cpp
@ -63,6 +64,7 @@ set(SRC_HEADERS
image_vdb.h
integrator.h
light.h
light_tree.h
jitter.h
mesh.h
object.h

View File

@ -187,7 +187,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
kfilm->pass_transmission_indirect = PASS_UNUSED;
kfilm->pass_volume_direct = PASS_UNUSED;
kfilm->pass_volume_indirect = PASS_UNUSED;
kfilm->pass_shadow = PASS_UNUSED;
kfilm->pass_lightgroup = PASS_UNUSED;
/* Mark passes as unused so that the kernel knows the pass is inaccessible. */
@ -295,9 +294,6 @@ void Film::device_update(Device *device, DeviceScene *dscene, Scene *scene)
case PASS_AO:
kfilm->pass_ao = kfilm->pass_stride;
break;
case PASS_SHADOW:
kfilm->pass_shadow = kfilm->pass_stride;
break;
case PASS_DIFFUSE_COLOR:
kfilm->pass_diffuse_color = kfilm->pass_stride;
@ -727,10 +723,6 @@ uint Film::get_kernel_features(const Scene *scene) const
kernel_features |= KERNEL_FEATURE_LIGHT_PASSES;
}
if (pass_type == PASS_SHADOW) {
kernel_features |= KERNEL_FEATURE_SHADOW_PASS;
}
if (pass_type == PASS_AO) {
kernel_features |= KERNEL_FEATURE_AO_PASS;
}

View File

@ -102,7 +102,8 @@ NODE_DEFINE(Integrator)
SOCKET_FLOAT(adaptive_threshold, "Adaptive Threshold", 0.01f);
SOCKET_INT(adaptive_min_samples, "Adaptive Min Samples", 0);
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.01f);
SOCKET_BOOLEAN(use_light_tree, "Use light tree to optimize many light sampling", true);
SOCKET_FLOAT(light_sampling_threshold, "Light Sampling Threshold", 0.0f);
static NodeEnum sampling_pattern_enum;
sampling_pattern_enum.insert("sobol_burley", SAMPLING_PATTERN_SOBOL_BURLEY);
@ -250,6 +251,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->sampling_pattern = sampling_pattern;
kintegrator->scrambling_distance = scrambling_distance;
kintegrator->use_light_tree = scene->integrator->use_light_tree;
if (light_sampling_threshold > 0.0f) {
kintegrator->light_inv_rr_threshold = 1.0f / light_sampling_threshold;
}

View File

@ -79,6 +79,7 @@ class Integrator : public Node {
NODE_SOCKET_API(int, aa_samples)
NODE_SOCKET_API(int, start_sample)
NODE_SOCKET_API(bool, use_light_tree)
NODE_SOCKET_API(float, light_sampling_threshold)
NODE_SOCKET_API(bool, use_adaptive_sampling)

View File

@ -110,6 +110,7 @@ NODE_DEFINE(Light)
SOCKET_FLOAT(spread, "Spread", M_PI_F);
SOCKET_INT(map_resolution, "Map Resolution", 0);
SOCKET_FLOAT(average_radiance, "Average Radiance", 0.0f);
SOCKET_FLOAT(spot_angle, "Spot Angle", M_PI_4_F);
SOCKET_FLOAT(spot_smooth, "Spot Smooth", 0.0f);
@ -265,13 +266,12 @@ bool LightManager::object_usable_as_light(Object *object)
return false;
}
void LightManager::device_update_distribution(Device *device,
void LightManager::device_update_distribution(Device *,
DeviceScene *dscene,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
KernelFilm *kfilm = &dscene->data.film;
/* Update CDF over lights. */
progress.set_status("Updating Lights", "Computing distribution");
@ -304,7 +304,6 @@ void LightManager::device_update_distribution(Device *device,
}
const size_t num_lights = kintegrator->num_lights;
const size_t num_background_lights = kintegrator->num_background_lights;
const size_t num_distribution = num_triangles + num_lights;
/* Distribution size. */
@ -312,6 +311,11 @@ void LightManager::device_update_distribution(Device *device,
VLOG_INFO << "Total " << num_distribution << " of light distribution primitives.";
if (kintegrator->use_light_tree) {
dscene->light_distribution.free();
return;
}
/* Emission area. */
KernelLightDistribution *distribution = dscene->light_distribution.alloc(num_distribution + 1);
float totarea = 0.0f;
@ -402,8 +406,8 @@ void LightManager::device_update_distribution(Device *device,
distribution[offset].totarea = totarea;
distribution[offset].prim = ~light_index;
distribution[offset].lamp.pad = 1.0f;
distribution[offset].lamp.size = light->size;
distribution[offset].mesh_light.object_id = OBJECT_NONE;
distribution[offset].mesh_light.shader_flag = 0;
totarea += lightarea;
light_index++;
@ -413,9 +417,9 @@ void LightManager::device_update_distribution(Device *device,
/* normalize cumulative distribution functions */
distribution[num_distribution].totarea = totarea;
distribution[num_distribution].prim = 0.0f;
distribution[num_distribution].lamp.pad = 0.0f;
distribution[num_distribution].lamp.size = 0.0f;
distribution[num_distribution].prim = 0;
distribution[num_distribution].mesh_light.object_id = OBJECT_NONE;
distribution[num_distribution].mesh_light.shader_flag = 0;
if (totarea > 0.0f) {
for (size_t i = 0; i < num_distribution; i++)
@ -448,22 +452,201 @@ void LightManager::device_update_distribution(Device *device,
}
}
/* bit of an ugly hack to compensate for emitting triangles influencing
* amount of samples we get for this pass */
kfilm->pass_shadow_scale = 1.0f;
if (kintegrator->distribution_pdf_triangles != 0.0f) {
kfilm->pass_shadow_scale /= 0.5f;
}
if (num_background_lights < num_lights) {
kfilm->pass_shadow_scale /= (float)(num_lights - num_background_lights) / (float)num_lights;
}
/* Copy distribution to device. */
dscene->light_distribution.copy_to_device();
}
void LightManager::device_update_tree(Device *,
DeviceScene *dscene,
Scene *scene,
Progress &progress)
{
KernelIntegrator *kintegrator = &dscene->data.integrator;
if (!kintegrator->use_light_tree) {
dscene->light_tree_nodes.free();
dscene->light_tree_emitters.free();
dscene->light_to_tree.free();
dscene->object_lookup_offset.free();
dscene->triangle_to_tree.free();
return;
}
/* Update light tree. */
progress.set_status("Updating Lights", "Computing tree");
/* Add both lights and emissive triangles to this vector for light tree construction. */
vector<LightTreePrimitive> light_prims;
light_prims.reserve(kintegrator->num_distribution);
vector<LightTreePrimitive> distant_lights;
distant_lights.reserve(kintegrator->num_distant_lights);
vector<uint> object_lookup_offsets(scene->objects.size());
/* When we keep track of the light index, only contributing lights will be added to the device.
* Therefore, we want to keep track of the light's index on the device.
* However, we also need the light's index in the scene when we're constructing the tree. */
int device_light_index = 0;
int scene_light_index = 0;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
if (light->light_type == LIGHT_BACKGROUND || light->light_type == LIGHT_DISTANT) {
distant_lights.emplace_back(scene, ~device_light_index, scene_light_index);
}
else {
light_prims.emplace_back(scene, ~device_light_index, scene_light_index);
}
device_light_index++;
}
scene_light_index++;
}
/* Similarly, we also want to keep track of the index of triangles that are emissive. */
size_t total_triangles = 0;
int object_id = 0;
foreach (Object *object, scene->objects) {
if (progress.get_cancel())
return;
if (!object_usable_as_light(object)) {
object_id++;
continue;
}
object_lookup_offsets[object_id] = total_triangles;
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->get_shader()[i];
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
light_prims.emplace_back(scene, i, object_id);
}
}
total_triangles += mesh_num_triangles;
object_id++;
}
/* Append distant lights to the end of `light_prims` */
std::move(distant_lights.begin(), distant_lights.end(), std::back_inserter(light_prims));
/* Update integrator state. */
kintegrator->use_direct_light = !light_prims.empty();
/* TODO: For now, we'll start with a smaller number of max lights in a node.
* More benchmarking is needed to determine what number works best. */
LightTree light_tree(light_prims, kintegrator->num_distant_lights, 8);
/* We want to create separate arrays corresponding to triangles and lights,
* which will be used to index back into the light tree for PDF calculations. */
const size_t num_lights = kintegrator->num_lights;
uint *light_array = dscene->light_to_tree.alloc(num_lights);
uint *object_offsets = dscene->object_lookup_offset.alloc(object_lookup_offsets.size());
uint *triangle_array = dscene->triangle_to_tree.alloc(total_triangles);
for (int i = 0; i < object_lookup_offsets.size(); i++) {
object_offsets[i] = object_lookup_offsets[i];
}
/* First initialize the light tree's nodes. */
const vector<LightTreeNode> &linearized_bvh = light_tree.get_nodes();
KernelLightTreeNode *light_tree_nodes = dscene->light_tree_nodes.alloc(linearized_bvh.size());
KernelLightTreeEmitter *light_tree_emitters = dscene->light_tree_emitters.alloc(
light_prims.size());
for (int index = 0; index < linearized_bvh.size(); index++) {
const LightTreeNode &node = linearized_bvh[index];
light_tree_nodes[index].energy = node.energy;
light_tree_nodes[index].bbox.min = node.bbox.min;
light_tree_nodes[index].bbox.max = node.bbox.max;
light_tree_nodes[index].bcone.axis = node.bcone.axis;
light_tree_nodes[index].bcone.theta_o = node.bcone.theta_o;
light_tree_nodes[index].bcone.theta_e = node.bcone.theta_e;
light_tree_nodes[index].bit_trail = node.bit_trail;
light_tree_nodes[index].num_prims = node.num_prims;
/* Here we need to make a distinction between interior and leaf nodes. */
if (node.is_leaf()) {
light_tree_nodes[index].child_index = -node.first_prim_index;
for (int i = 0; i < node.num_prims; i++) {
int emitter_index = i + node.first_prim_index;
LightTreePrimitive &prim = light_prims[emitter_index];
light_tree_emitters[emitter_index].energy = prim.energy;
light_tree_emitters[emitter_index].theta_o = prim.bcone.theta_o;
light_tree_emitters[emitter_index].theta_e = prim.bcone.theta_e;
if (prim.is_triangle()) {
light_tree_emitters[emitter_index].mesh_light.object_id = prim.object_id;
int shader_flag = 0;
Object *object = scene->objects[prim.object_id];
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
Shader *shader = static_cast<Shader *>(
mesh->get_used_shaders()[mesh->get_shader()[prim.prim_id]]);
if (!(object->get_visibility() & PATH_RAY_CAMERA)) {
shader_flag |= SHADER_EXCLUDE_CAMERA;
}
if (!(object->get_visibility() & PATH_RAY_DIFFUSE)) {
shader_flag |= SHADER_EXCLUDE_DIFFUSE;
}
if (!(object->get_visibility() & PATH_RAY_GLOSSY)) {
shader_flag |= SHADER_EXCLUDE_GLOSSY;
}
if (!(object->get_visibility() & PATH_RAY_TRANSMIT)) {
shader_flag |= SHADER_EXCLUDE_TRANSMIT;
}
if (!(object->get_visibility() & PATH_RAY_VOLUME_SCATTER)) {
shader_flag |= SHADER_EXCLUDE_SCATTER;
}
if (!(object->get_is_shadow_catcher())) {
shader_flag |= SHADER_EXCLUDE_SHADOW_CATCHER;
}
light_tree_emitters[emitter_index].prim_id = prim.prim_id + mesh->prim_offset;
light_tree_emitters[emitter_index].mesh_light.shader_flag = shader_flag;
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
shader->emission_sampling;
triangle_array[prim.prim_id + object_lookup_offsets[prim.object_id]] = emitter_index;
}
else {
light_tree_emitters[emitter_index].prim_id = prim.prim_id;
light_tree_emitters[emitter_index].mesh_light.shader_flag = 0;
light_tree_emitters[emitter_index].mesh_light.object_id = OBJECT_NONE;
light_tree_emitters[emitter_index].mesh_light.emission_sampling =
EMISSION_SAMPLING_FRONT_BACK;
light_array[~prim.prim_id] = emitter_index;
}
light_tree_emitters[emitter_index].parent_index = index;
}
}
else {
light_tree_nodes[index].child_index = node.right_child_index;
}
}
/* Copy arrays to device. */
dscene->light_tree_nodes.copy_to_device();
dscene->light_tree_emitters.copy_to_device();
dscene->light_to_tree.copy_to_device();
dscene->object_lookup_offset.copy_to_device();
dscene->triangle_to_tree.copy_to_device();
}
static void background_cdf(
int start, int end, int res_x, int res_y, const vector<float3> *pixels, float2 *cond_cdf)
{
@ -650,6 +833,8 @@ void LightManager::device_update_background(Device *device,
float cdf_total = marg_cdf[res.y - 1].y + marg_cdf[res.y - 1].x / res.y;
marg_cdf[res.y].x = cdf_total;
background_light->set_average_radiance(cdf_total * M_PI_2_F);
if (cdf_total > 0.0f)
for (int i = 1; i < res.y; i++)
marg_cdf[i].y /= cdf_total;
@ -663,7 +848,7 @@ void LightManager::device_update_background(Device *device,
dscene->light_background_conditional_cdf.copy_to_device();
}
void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *scene)
void LightManager::device_update_lights(Device *device, DeviceScene *dscene, Scene *scene)
{
/* Counts lights in the scene. */
size_t num_lights = 0;
@ -697,6 +882,8 @@ void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *sc
/* Update integrator settings. */
KernelIntegrator *kintegrator = &dscene->data.integrator;
kintegrator->use_light_tree = scene->integrator->get_use_light_tree() &&
device->info.has_light_tree;
kintegrator->num_lights = num_lights;
kintegrator->num_distant_lights = num_distant_lights;
kintegrator->num_background_lights = num_background_lights;
@ -851,14 +1038,14 @@ void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *sc
float invarea = (area != 0.0f) ? 1.0f / area : 1.0f;
float3 dir = light->dir;
/* Convert from spread angle 0..180 to 90..0, clamping to a minimum
* angle to avoid excessive noise. */
const float min_spread_angle = 1.0f * M_PI_F / 180.0f;
const float spread_angle = 0.5f * (M_PI_F - max(light->spread, min_spread_angle));
/* Clamping to a minimum angle to avoid excessive noise. */
const float min_spread = 1.0f * M_PI_F / 180.0f;
const float half_spread = 0.5f * max(light->spread, min_spread);
/* cot_half_spread is h in D10594#269626 */
const float cot_half_spread = tanf(M_PI_2_F - half_spread);
/* Normalization computed using:
* integrate cos(x) * (1 - tan(x) * tan(a)) * sin(x) from x = 0 to pi/2 - a. */
const float tan_spread = tanf(spread_angle);
const float normalize_spread = 2.0f / (2.0f + (2.0f * spread_angle - M_PI_F) * tan_spread);
* integrate cos(x) * (1 - tan(x) / tan(a)) * sin(x) from x = 0 to a, a being half_spread */
const float normalize_spread = 1.0f / (1.0f - half_spread * cot_half_spread);
dir = safe_normalize(dir);
@ -872,7 +1059,7 @@ void LightManager::device_update_lights(Device *, DeviceScene *dscene, Scene *sc
klights[light_index].area.len_v = len_v;
klights[light_index].area.invarea = invarea;
klights[light_index].area.dir = dir;
klights[light_index].area.tan_spread = tan_spread;
klights[light_index].area.cot_half_spread = cot_half_spread;
klights[light_index].area.normalize_spread = normalize_spread;
}
else if (light->light_type == LIGHT_SPOT) {
@ -957,6 +1144,10 @@ void LightManager::device_update(Device *device,
if (progress.get_cancel())
return;
device_update_tree(device, dscene, scene, progress);
if (progress.get_cancel())
return;
device_update_ies(dscene);
if (progress.get_cancel())
return;
@ -967,6 +1158,12 @@ void LightManager::device_update(Device *device,
void LightManager::device_free(Device *, DeviceScene *dscene, const bool free_background)
{
/* to-do: check if the light tree member variables need to be wrapped in a conditional too*/
dscene->light_tree_nodes.free();
dscene->light_tree_emitters.free();
dscene->light_to_tree.free();
dscene->triangle_to_tree.free();
dscene->light_distribution.free();
dscene->lights.free();
if (free_background) {

View File

@ -10,6 +10,7 @@
/* included as Light::set_shader defined through NODE_SOCKET_API does not select
* the right Node::set overload as it does not know that Shader is a Node */
#include "scene/light_tree.h"
#include "scene/shader.h"
#include "util/ies.h"
@ -50,6 +51,7 @@ class Light : public Node {
NODE_SOCKET_API(Transform, tfm)
NODE_SOCKET_API(int, map_resolution)
NODE_SOCKET_API(float, average_radiance)
NODE_SOCKET_API(float, spot_angle)
NODE_SOCKET_API(float, spot_smooth)

View File

@ -0,0 +1,390 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#include "scene/light_tree.h"
#include "scene/mesh.h"
#include "scene/object.h"
CCL_NAMESPACE_BEGIN
float OrientationBounds::calculate_measure() const
{
float theta_w = fminf(M_PI_F, theta_o + theta_e);
float cos_theta_o = cosf(theta_o);
float sin_theta_o = sinf(theta_o);
return M_2PI_F * (1 - cos_theta_o) +
M_PI_2_F * (2 * theta_w * sin_theta_o - cosf(theta_o - 2 * theta_w) -
2 * theta_o * sin_theta_o + cos_theta_o);
}
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b)
{
if (is_zero(cone_a.axis)) {
return cone_b;
}
if (is_zero(cone_b.axis)) {
return cone_a;
}
/* Set cone a to always have the greater theta_o. */
const OrientationBounds *a = &cone_a;
const OrientationBounds *b = &cone_b;
if (cone_b.theta_o > cone_a.theta_o) {
a = &cone_b;
b = &cone_a;
}
float theta_d = safe_acosf(dot(a->axis, b->axis));
float theta_e = fmaxf(a->theta_e, b->theta_e);
/* Return axis and theta_o of a if it already contains b. */
/* This should also be called when b is empty. */
if (a->theta_o >= fminf(M_PI_F, theta_d + b->theta_o)) {
return OrientationBounds({a->axis, a->theta_o, theta_e});
}
/* Compute new theta_o that contains both a and b. */
float theta_o = (theta_d + a->theta_o + b->theta_o) * 0.5f;
if (theta_o >= M_PI_F) {
return OrientationBounds({a->axis, M_PI_F, theta_e});
}
/* Rotate new axis to be between a and b. */
float theta_r = theta_o - a->theta_o;
float3 new_axis = rotate_around_axis(a->axis, cross(a->axis, b->axis), theta_r);
new_axis = normalize(new_axis);
return OrientationBounds({new_axis, theta_o, theta_e});
}
LightTreePrimitive::LightTreePrimitive(Scene *scene, int prim_id, int object_id)
: prim_id(prim_id), object_id(object_id)
{
bcone = OrientationBounds::empty;
bbox = BoundBox::empty;
if (is_triangle()) {
float3 vertices[3];
Object *object = scene->objects[object_id];
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
Mesh::Triangle triangle = mesh->get_triangle(prim_id);
Shader *shader = static_cast<Shader *>(mesh->get_used_shaders()[mesh->get_shader()[prim_id]]);
for (int i = 0; i < 3; i++) {
vertices[i] = mesh->get_verts()[triangle.v[i]];
}
/* instanced mesh lights have not applied their transform at this point.
* in this case, these points have to be transformed to get the proper
* spatial bound. */
if (!mesh->transform_applied) {
const Transform &tfm = object->get_tfm();
for (int i = 0; i < 3; i++) {
vertices[i] = transform_point(&tfm, vertices[i]);
}
}
/* TODO: need a better way to handle this when textures are used. */
float area = triangle_area(vertices[0], vertices[1], vertices[2]);
energy = area * average(shader->emission_estimate);
/* NOTE: the original implementation used the bounding box centroid, but primitive centroid
* seems to work fine */
centroid = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
if (shader->emission_sampling == EMISSION_SAMPLING_FRONT) {
/* Front only. */
bcone.axis = safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
bcone.theta_o = 0;
}
else if (shader->emission_sampling == EMISSION_SAMPLING_BACK) {
/* Back only. */
bcone.axis = -safe_normalize(cross(vertices[1] - vertices[0], vertices[2] - vertices[0]));
bcone.theta_o = 0;
}
else {
/* Double sided: any vector in the plane. */
bcone.axis = safe_normalize(vertices[0] - vertices[1]);
bcone.theta_o = M_PI_2_F;
}
bcone.theta_e = M_PI_2_F;
for (int i = 0; i < 3; i++) {
bbox.grow(vertices[i]);
}
}
else {
Light *lamp = scene->lights[object_id];
LightType type = lamp->get_light_type();
const float size = lamp->get_size();
float3 strength = lamp->get_strength();
centroid = scene->lights[object_id]->get_co();
bcone.axis = normalize(lamp->get_dir());
if (type == LIGHT_AREA) {
bcone.theta_o = 0;
bcone.theta_e = lamp->get_spread() * 0.5f;
/* For an area light, sizeu and sizev determine the 2 dimensions of the area light,
* while axisu and axisv determine the orientation of the 2 dimensions.
* We want to add all 4 corners to our bounding box. */
const float3 half_extentu = 0.5f * lamp->get_sizeu() * lamp->get_axisu() * size;
const float3 half_extentv = 0.5f * lamp->get_sizev() * lamp->get_axisv() * size;
bbox.grow(centroid + half_extentu + half_extentv);
bbox.grow(centroid + half_extentu - half_extentv);
bbox.grow(centroid - half_extentu + half_extentv);
bbox.grow(centroid - half_extentu - half_extentv);
strength *= 0.25f; /* eval_fac scaling in `area.h` */
}
else if (type == LIGHT_POINT) {
bcone.theta_o = M_PI_F;
bcone.theta_e = M_PI_2_F;
/* Point and spot lights can emit light from any point within its radius. */
const float3 radius = make_float3(size);
bbox.grow(centroid - radius);
bbox.grow(centroid + radius);
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
}
else if (type == LIGHT_SPOT) {
bcone.theta_o = 0;
bcone.theta_e = lamp->get_spot_angle() * 0.5f;
/* Point and spot lights can emit light from any point within its radius. */
const float3 radius = make_float3(size);
bbox.grow(centroid - radius);
bbox.grow(centroid + radius);
strength *= 0.25f * M_1_PI_F; /* eval_fac scaling in `spot.h` and `point.h` */
}
else if (type == LIGHT_BACKGROUND) {
/* Set an arbitrary direction for the background light. */
bcone.axis = make_float3(0.0f, 0.0f, 1.0f);
/* TODO: this may depend on portal lights as well. */
bcone.theta_o = M_PI_F;
bcone.theta_e = 0;
/* integrate over cosine-weighted hemisphere */
strength *= lamp->get_average_radiance() * M_PI_F;
}
else if (type == LIGHT_DISTANT) {
bcone.theta_o = 0;
bcone.theta_e = 0.5f * lamp->get_angle();
}
if (lamp->get_shader()) {
strength *= lamp->get_shader()->emission_estimate;
}
energy = average(strength);
}
}
LightTree::LightTree(vector<LightTreePrimitive> &prims,
const int &num_distant_lights,
uint max_lights_in_leaf)
{
if (prims.empty()) {
return;
}
max_lights_in_leaf_ = max_lights_in_leaf;
int num_prims = prims.size();
int num_local_lights = num_prims - num_distant_lights;
/* The amount of nodes is estimated to be twice the amount of primitives */
nodes_.reserve(2 * num_prims);
nodes_.emplace_back(); /* root node */
recursive_build(0, num_local_lights, prims, 0, 1); /* build tree */
nodes_[0].make_interior(nodes_.size());
/* All distant lights are grouped to one node (right child of the root node) */
OrientationBounds bcone = OrientationBounds::empty;
float energy_total = 0.0;
for (int i = num_local_lights; i < num_prims; i++) {
const LightTreePrimitive &prim = prims.at(i);
bcone = merge(bcone, prim.bcone);
energy_total += prim.energy;
}
nodes_.emplace_back(BoundBox::empty, bcone, energy_total, 1);
nodes_.back().make_leaf(num_local_lights, num_distant_lights);
nodes_.shrink_to_fit();
}
const vector<LightTreeNode> &LightTree::get_nodes() const
{
return nodes_;
}
int LightTree::recursive_build(
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth)
{
BoundBox bbox = BoundBox::empty;
OrientationBounds bcone = OrientationBounds::empty;
BoundBox centroid_bounds = BoundBox::empty;
float energy_total = 0.0;
int num_prims = end - start;
int current_index = nodes_.size();
for (int i = start; i < end; i++) {
const LightTreePrimitive &prim = prims.at(i);
bbox.grow(prim.bbox);
bcone = merge(bcone, prim.bcone);
centroid_bounds.grow(prim.centroid);
energy_total += prim.energy;
}
nodes_.emplace_back(bbox, bcone, energy_total, bit_trail);
bool try_splitting = num_prims > 1 && len(centroid_bounds.size()) > 0.0f;
int split_dim = -1, split_bucket = 0, num_left_prims = 0;
bool should_split = false;
if (try_splitting) {
/* Find the best place to split the primitives into 2 nodes.
* If the best split cost is no better than making a leaf node, make a leaf instead.*/
float min_cost = min_split_saoh(
centroid_bounds, start, end, bbox, bcone, split_dim, split_bucket, num_left_prims, prims);
should_split = num_prims > max_lights_in_leaf_ || min_cost < energy_total;
}
if (should_split) {
int middle;
if (split_dim != -1) {
/* Partition the primitives between start and end based on the split dimension and bucket
* calculated by `split_saoh` */
middle = start + num_left_prims;
std::nth_element(prims.begin() + start,
prims.begin() + middle,
prims.begin() + end,
[split_dim](const LightTreePrimitive &l, const LightTreePrimitive &r) {
return l.centroid[split_dim] < r.centroid[split_dim];
});
}
else {
/* Degenerate case with many lights in the same place. */
middle = (start + end) / 2;
}
[[maybe_unused]] int left_index = recursive_build(start, middle, prims, bit_trail, depth + 1);
int right_index = recursive_build(middle, end, prims, bit_trail | (1u << depth), depth + 1);
assert(left_index == current_index + 1);
nodes_[current_index].make_interior(right_index);
}
else {
nodes_[current_index].make_leaf(start, num_prims);
}
return current_index;
}
float LightTree::min_split_saoh(const BoundBox &centroid_bbox,
int start,
int end,
const BoundBox &bbox,
const OrientationBounds &bcone,
int &split_dim,
int &split_bucket,
int &num_left_prims,
const vector<LightTreePrimitive> &prims)
{
/* Even though this factor is used for every bucket, we use it to compare
* the min_cost and total_energy (when deciding between creating a leaf or interior node. */
const float bbox_area = bbox.area();
const bool has_area = bbox_area != 0.0f;
const float total_area = has_area ? bbox_area : len(bbox.size());
const float total_cost = total_area * bcone.calculate_measure();
if (total_cost == 0.0f) {
return FLT_MAX;
}
const float inv_total_cost = 1.0f / total_cost;
const float3 extent = centroid_bbox.size();
const float max_extent = max4(extent.x, extent.y, extent.z, 0.0f);
/* Check each dimension to find the minimum splitting cost. */
float min_cost = FLT_MAX;
for (int dim = 0; dim < 3; dim++) {
/* If the centroid bounding box is 0 along a given dimension, skip it. */
if (centroid_bbox.size()[dim] == 0.0f) {
continue;
}
const float inv_extent = 1 / (centroid_bbox.size()[dim]);
/* Fill in buckets with primitives. */
vector<LightTreeBucketInfo> buckets(LightTreeBucketInfo::num_buckets);
for (int i = start; i < end; i++) {
const LightTreePrimitive &prim = prims[i];
/* Place primitive into the appropriate bucket,
* where the centroid box is split into equal partitions. */
int bucket_idx = LightTreeBucketInfo::num_buckets *
(prim.centroid[dim] - centroid_bbox.min[dim]) * inv_extent;
if (bucket_idx == LightTreeBucketInfo::num_buckets) {
bucket_idx = LightTreeBucketInfo::num_buckets - 1;
}
buckets[bucket_idx].count++;
buckets[bucket_idx].energy += prim.energy;
buckets[bucket_idx].bbox.grow(prim.bbox);
buckets[bucket_idx].bcone = merge(buckets[bucket_idx].bcone, prim.bcone);
}
/* Calculate the cost of splitting at each point between partitions. */
vector<float> bucket_costs(LightTreeBucketInfo::num_buckets - 1);
float energy_L, energy_R;
BoundBox bbox_L, bbox_R;
OrientationBounds bcone_L, bcone_R;
for (int split = 0; split < LightTreeBucketInfo::num_buckets - 1; split++) {
energy_L = 0;
energy_R = 0;
bbox_L = BoundBox::empty;
bbox_R = BoundBox::empty;
bcone_L = OrientationBounds::empty;
bcone_R = OrientationBounds::empty;
for (int left = 0; left <= split; left++) {
if (buckets[left].bbox.valid()) {
energy_L += buckets[left].energy;
bbox_L.grow(buckets[left].bbox);
bcone_L = merge(bcone_L, buckets[left].bcone);
}
}
for (int right = split + 1; right < LightTreeBucketInfo::num_buckets; right++) {
if (buckets[right].bbox.valid()) {
energy_R += buckets[right].energy;
bbox_R.grow(buckets[right].bbox);
bcone_R = merge(bcone_R, buckets[right].bcone);
}
}
/* Calculate the cost of splitting using the heuristic as described in the paper. */
const float area_L = has_area ? bbox_L.area() : len(bbox_L.size());
const float area_R = has_area ? bbox_R.area() : len(bbox_R.size());
float left = (bbox_L.valid()) ? energy_L * area_L * bcone_L.calculate_measure() : 0.0f;
float right = (bbox_R.valid()) ? energy_R * area_R * bcone_R.calculate_measure() : 0.0f;
float regularization = max_extent * inv_extent;
bucket_costs[split] = regularization * (left + right) * inv_total_cost;
if (bucket_costs[split] < min_cost) {
min_cost = bucket_costs[split];
split_dim = dim;
split_bucket = split;
num_left_prims = 0;
for (int i = 0; i <= split_bucket; i++) {
num_left_prims += buckets[i].count;
}
}
}
}
return min_cost;
}
CCL_NAMESPACE_END

View File

@ -0,0 +1,160 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#ifndef __LIGHT_TREE_H__
#define __LIGHT_TREE_H__
#include "scene/light.h"
#include "scene/scene.h"
#include "util/boundbox.h"
#include "util/types.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
/* Orientation Bounds
*
* Bounds the normal axis of the lights,
* along with their emission profiles */
struct OrientationBounds {
float3 axis; /* normal axis of the light */
float theta_o; /* angle bounding the normals */
float theta_e; /* angle bounding the light emissions */
__forceinline OrientationBounds()
{
}
__forceinline OrientationBounds(const float3 &axis_, float theta_o_, float theta_e_)
: axis(axis_), theta_o(theta_o_), theta_e(theta_e_)
{
}
enum empty_t { empty = 0 };
/* If the orientation bound is set to empty, the values are set to minumums
* so that merging it with another non-empty orientation bound guarantees that
* the return value is equal to non-empty orientation bound. */
__forceinline OrientationBounds(empty_t)
: axis(make_float3(0, 0, 0)), theta_o(FLT_MIN), theta_e(FLT_MIN)
{
}
float calculate_measure() const;
};
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b);
/* --------------------------------------------------------------------
* Light Tree Construction
*
* The light tree construction is based on PBRT's BVH construction.
*/
/* Light Tree Primitive
* Struct that indexes into the scene's triangle and light arrays. */
struct LightTreePrimitive {
/* `prim_id >= 0` is an index into an object's local triangle index,
* otherwise `-prim_id-1`(`~prim`) is an index into device lights array. */
int prim_id;
int object_id;
float energy;
float3 centroid;
OrientationBounds bcone;
BoundBox bbox;
LightTreePrimitive(Scene *scene, int prim_id, int object_id);
inline bool is_triangle() const
{
return prim_id >= 0;
};
};
/* Light Tree Bucket Info
* Struct used to determine splitting costs in the light BVH. */
struct LightTreeBucketInfo {
LightTreeBucketInfo()
: energy(0.0f), bbox(BoundBox::empty), bcone(OrientationBounds::empty), count(0)
{
}
float energy; /* Total energy in the partition */
BoundBox bbox;
OrientationBounds bcone;
int count;
static const int num_buckets = 12;
};
/* Light Tree Node */
struct LightTreeNode {
BoundBox bbox;
OrientationBounds bcone;
float energy;
uint bit_trail;
int num_prims = -1;
union {
int first_prim_index; /* leaf nodes contain an index to first primitive. */
int right_child_index; /* interior nodes contain an index to second child. */
};
LightTreeNode() = default;
LightTreeNode(const BoundBox &bbox,
const OrientationBounds &bcone,
const float &energy,
const uint &bit_trial)
: bbox(bbox), bcone(bcone), energy(energy), bit_trail(bit_trial)
{
}
void make_leaf(const uint &first_prim_index, const int &num_prims)
{
this->first_prim_index = first_prim_index;
this->num_prims = num_prims;
}
void make_interior(const int &right_child_index)
{
this->right_child_index = right_child_index;
}
inline bool is_leaf() const
{
return num_prims >= 0;
}
};
/* Light BVH
*
* BVH-like data structure that keeps track of lights
* and considers additional orientation and energy information */
class LightTree {
vector<LightTreeNode> nodes_;
uint max_lights_in_leaf_;
public:
LightTree(vector<LightTreePrimitive> &prims,
const int &num_distant_lights,
uint max_lights_in_leaf);
const vector<LightTreeNode> &get_nodes() const;
private:
int recursive_build(
int start, int end, vector<LightTreePrimitive> &prims, uint bit_trail, int depth);
float min_split_saoh(const BoundBox &centroid_bbox,
int start,
int end,
const BoundBox &bbox,
const OrientationBounds &bcone,
int &split_dim,
int &split_bucket,
int &num_left_prims,
const vector<LightTreePrimitive> &prims);
};
CCL_NAMESPACE_END
#endif /* __LIGHT_TREE_H__ */

View File

@ -565,10 +565,12 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
void ObjectManager::device_update_prim_offsets(Device *device, DeviceScene *dscene, Scene *scene)
{
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
return;
if (!scene->integrator->get_use_light_tree()) {
BVHLayoutMask layout_mask = device->get_bvh_layout_mask();
if (layout_mask != BVH_LAYOUT_METAL && layout_mask != BVH_LAYOUT_MULTI_METAL &&
layout_mask != BVH_LAYOUT_MULTI_METAL_EMBREE) {
return;
}
}
/* On MetalRT, primitive / curve segment offsets can't be baked at BVH build time. Intersection

View File

@ -52,7 +52,6 @@ const NodeEnum *Pass::get_type_enum()
pass_type_enum.insert("emission", PASS_EMISSION);
pass_type_enum.insert("background", PASS_BACKGROUND);
pass_type_enum.insert("ao", PASS_AO);
pass_type_enum.insert("shadow", PASS_SHADOW);
pass_type_enum.insert("diffuse", PASS_DIFFUSE);
pass_type_enum.insert("diffuse_direct", PASS_DIFFUSE_DIRECT);
pass_type_enum.insert("diffuse_indirect", PASS_DIFFUSE_INDIRECT);
@ -209,10 +208,6 @@ PassInfo Pass::get_info(const PassType type, const bool include_albedo, const bo
case PASS_AO:
pass_info.num_components = 3;
break;
case PASS_SHADOW:
pass_info.num_components = 3;
pass_info.use_exposure = false;
break;
case PASS_DIFFUSE_COLOR:
case PASS_GLOSSY_COLOR:

View File

@ -71,6 +71,11 @@ DeviceScene::DeviceScene(Device *device)
lights(device, "lights", MEM_GLOBAL),
light_background_marginal_cdf(device, "light_background_marginal_cdf", MEM_GLOBAL),
light_background_conditional_cdf(device, "light_background_conditional_cdf", MEM_GLOBAL),
light_tree_nodes(device, "light_tree_nodes", MEM_GLOBAL),
light_tree_emitters(device, "light_tree_emitters", MEM_GLOBAL),
light_to_tree(device, "light_to_tree", MEM_GLOBAL),
object_lookup_offset(device, "object_lookup_offset", MEM_GLOBAL),
triangle_to_tree(device, "triangle_to_tree", MEM_GLOBAL),
particles(device, "particles", MEM_GLOBAL),
svm_nodes(device, "svm_nodes", MEM_GLOBAL),
shaders(device, "shaders", MEM_GLOBAL),

View File

@ -111,6 +111,13 @@ class DeviceScene {
device_vector<float2> light_background_marginal_cdf;
device_vector<float2> light_background_conditional_cdf;
/* light tree */
device_vector<KernelLightTreeNode> light_tree_nodes;
device_vector<KernelLightTreeEmitter> light_tree_emitters;
device_vector<uint> light_to_tree;
device_vector<uint> object_lookup_offset;
device_vector<uint> triangle_to_tree;
/* particles */
device_vector<KernelParticle> particles;

View File

@ -1195,7 +1195,7 @@ int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_context,
#ifdef WITH_VULKAN_BACKEND
/**
* Return vulkan handles for the given context.
* Return VULKAN handles for the given context.
*/
void GHOST_GetVulkanHandles(GHOST_ContextHandle context,
void *r_instance,
@ -1204,7 +1204,7 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle context,
uint32_t *r_graphic_queue_familly);
/**
* Return vulkan backbuffer resources handles for the given window.
* Return VULKAN back-buffer resources handles for the given window.
*/
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
void *image,

View File

@ -1199,9 +1199,6 @@ int GHOST_XrGetControllerModelData(GHOST_XrContextHandle xr_contexthandle,
#ifdef WITH_VULKAN_BACKEND
/**
* Return vulkan handles for the given context.
*/
void GHOST_GetVulkanHandles(GHOST_ContextHandle contexthandle,
void *r_instance,
void *r_physical_device,
@ -1212,9 +1209,6 @@ void GHOST_GetVulkanHandles(GHOST_ContextHandle contexthandle,
context->getVulkanHandles(r_instance, r_physical_device, r_device, r_graphic_queue_familly);
}
/**
* Return vulkan backbuffer resources handles for the given window.
*/
void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
void *image,
void *framebuffer,
@ -1227,4 +1221,4 @@ void GHOST_GetVulkanBackbuffer(GHOST_WindowHandle windowhandle,
window->getVulkanBackbuffer(image, framebuffer, command_buffer, render_pass, extent, fb_id);
}
#endif /* WITH_VULKAN */
#endif /* WITH_VULKAN */

View File

@ -101,7 +101,7 @@ bool win32_chk(bool result, const char *file, int line, const char *text)
# ifndef NDEBUG
_ftprintf(
stderr, "%s(%d):[%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
stderr, "%s:%d: [%s] -> Win32 Error# (%lu): %s", file, line, text, ulong(error), msg);
# else
_ftprintf(stderr, "Win32 Error# (%lu): %s", ulong(error), msg);
# endif

View File

@ -123,7 +123,7 @@ static bool egl_chk(bool result,
#ifndef NDEBUG
fprintf(stderr,
"%s(%d):[%s] -> EGL Error (0x%04X): %s: %s\n",
"%s:%d: [%s] -> EGL Error (0x%04X): %s: %s\n",
file,
line,
text,

View File

@ -100,7 +100,7 @@ static const char *vulkan_error_as_string(VkResult result)
printf(__VA_ARGS__); \
}
/* Tripple buffering. */
/* Triple buffering. */
const int MAX_FRAMES_IN_FLIGHT = 2;
GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual,
@ -220,7 +220,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
&m_currentImage);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
/* Swapchain is out of date. Recreate swapchain and skip this frame. */
/* Swap-chain is out of date. Recreate swap-chain and skip this frame. */
destroySwapchain();
createSwapchain();
return GHOST_kSuccess;
@ -271,7 +271,7 @@ GHOST_TSuccess GHOST_ContextVK::swapBuffers()
result = vkQueuePresentKHR(m_present_queue, &present_info);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
/* Swapchain is out of date. Recreate swapchain and skip this frame. */
/* Swap-chain is out of date. Recreate swap-chain and skip this frame. */
destroySwapchain();
createSwapchain();
return GHOST_kSuccess;
@ -932,7 +932,7 @@ GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
present_queue_create_info.queueCount = 1;
present_queue_create_info.pQueuePriorities = queue_priorities;
/* Eash queue must be unique. */
/* Each queue must be unique. */
if (m_queue_family_graphic != m_queue_family_present) {
queue_create_infos.push_back(present_queue_create_info);
}

View File

@ -1049,9 +1049,9 @@ void GHOST_SystemWin32::processPointerEvent(
}
}
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window)
GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *window,
const int32_t screen_co[2])
{
int32_t x_screen, y_screen;
GHOST_SystemWin32 *system = (GHOST_SystemWin32 *)getSystem();
if (window->getTabletData().Active != GHOST_kTabletModeNone) {
@ -1059,8 +1059,7 @@ GHOST_EventCursor *GHOST_SystemWin32::processCursorEvent(GHOST_WindowWin32 *wind
return NULL;
}
system->getCursorPosition(x_screen, y_screen);
int32_t x_screen = screen_co[0], y_screen = screen_co[1];
if (window->getCursorGrabModeIsWarp()) {
/* WORKAROUND:
* Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
@ -1834,7 +1833,10 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
}
}
event = processCursorEvent(window);
const int32_t window_co[2] = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
int32_t screen_co[2];
window->clientToScreen(UNPACK2(window_co), UNPACK2(screen_co));
event = processCursorEvent(window, screen_co);
break;
}
@ -1876,7 +1878,10 @@ LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam,
WINTAB_PRINTF("HWND %p mouse leave\n", window->getHWND());
window->m_mousePresent = false;
if (window->getTabletData().Active == GHOST_kTabletModeNone) {
event = processCursorEvent(window);
/* FIXME: document why the cursor motion event on mouse leave is needed. */
int32_t screen_co[2] = {0, 0};
system->getCursorPosition(screen_co[0], screen_co[1]);
event = processCursorEvent(window, screen_co);
}
GHOST_Wintab *wt = window->getWintab();
if (wt) {

View File

@ -331,7 +331,8 @@ class GHOST_SystemWin32 : public GHOST_System {
* \param window: The window receiving the event (the active window).
* \return The event created.
*/
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window);
static GHOST_EventCursor *processCursorEvent(GHOST_WindowWin32 *window,
const int32_t screen_co[2]);
/**
* Handles a mouse wheel event.

View File

@ -9,7 +9,7 @@ __all__ = (
)
import bpy
from typing import Mapping, List, Tuple, Sequence
from typing import Mapping, List, Tuple, Sequence
# (fcurve.data_path, fcurve.array_index)
FCurveKey = Tuple[str, int]
@ -17,6 +17,7 @@ FCurveKey = Tuple[str, int]
ListKeyframes = List[float]
Action = bpy.types.Action
def bake_action(
obj,
*,
@ -430,6 +431,7 @@ def bake_action_iter(
yield action
class KeyframesCo:
"""A buffer for keyframe Co unpacked values per FCurveKey. FCurveKeys are added using
add_paths(), Co values stored using extend_co_values(), then finally use

View File

@ -2137,9 +2137,6 @@ def km_node_editor(params):
)),
("transform.rotate", {"type": 'R', "value": 'PRESS'}, None),
("transform.resize", {"type": 'S', "value": 'PRESS'}, None),
("node.move_detach_links",
{"type": 'D', "value": 'PRESS', "alt": True},
{"properties": [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])]}),
("node.move_detach_links_release",
{"type": params.action_mouse, "value": 'CLICK_DRAG', "alt": True},
{"properties": [("NODE_OT_translate_attach", [("TRANSFORM_OT_translate", [("view2d_edge_pan", True)])])]}),

View File

@ -112,7 +112,10 @@ class ANIM_OT_keying_set_export(Operator):
break
if not found:
self.report({'WARN'}, tip_("Could not find material or light using Shader Node Tree - %s") % (ksp.id))
self.report(
{'WARN'},
tip_("Could not find material or light using Shader Node Tree - %s") %
(ksp.id))
elif ksp.id.bl_rna.identifier.startswith("CompositorNodeTree"):
# Find compositor nodetree using this node tree...
for scene in bpy.data.scenes:
@ -335,7 +338,7 @@ class ClearUselessActions(Operator):
removed += 1
self.report({'INFO'}, tip_("Removed %d empty and/or fake-user only Actions")
% removed)
% removed)
return {'FINISHED'}

View File

@ -3083,7 +3083,13 @@ class WM_MT_splash_quick_setup(Menu):
old_version = bpy.types.PREFERENCES_OT_copy_prev.previous_version()
if bpy.types.PREFERENCES_OT_copy_prev.poll(context) and old_version:
sub.operator("preferences.copy_prev", text=iface_("Load %d.%d Settings", "Operator") % old_version, translate=False)
sub.operator(
"preferences.copy_prev",
text=iface_(
"Load %d.%d Settings",
"Operator") %
old_version,
translate=False)
sub.operator("wm.save_userpref", text="Save New Settings")
else:
sub.label()

View File

@ -88,8 +88,16 @@ class CURVES_UL_attributes(UIList):
flags = []
indices = [i for i in range(len(attributes))]
for item in attributes:
flags.append(0 if item.is_internal else self.bitflag_filter_item)
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
# Filtering internal attributes
for idx, item in enumerate(attributes):
flags[idx] = 0 if item.is_internal else flags[idx]
return flags, indices

View File

@ -8,6 +8,7 @@ from bpy.app.translations import (
pgettext_tip as tip_,
)
class MESH_MT_vertex_group_context_menu(Menu):
bl_label = "Vertex Group Specials"
@ -536,8 +537,16 @@ class MESH_UL_attributes(UIList):
flags = []
indices = [i for i in range(len(attributes))]
for item in attributes:
flags.append(0 if item.is_internal else self.bitflag_filter_item)
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
# Filtering internal attributes
for idx, item in enumerate(attributes):
flags[idx] = 0 if item.is_internal else flags[idx]
return flags, indices
@ -625,20 +634,26 @@ class ColorAttributesListBase():
}
def filter_items(self, _context, data, property):
attrs = getattr(data, property)
ret = []
idxs = []
attributes = getattr(data, property)
flags = []
indices = [i for i in range(len(attributes))]
for idx, item in enumerate(attrs):
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
for idx, item in enumerate(attributes):
skip = (
(item.domain not in {"POINT", "CORNER"}) or
(item.data_type not in {"FLOAT_COLOR", "BYTE_COLOR"}) or
item.is_internal
)
ret.append(0 if skip else self.bitflag_filter_item)
idxs.append(idx)
flags[idx] = 0 if skip else flags[idx]
return ret, idxs
return flags, indices
class MESH_UL_color_attributes(UIList, ColorAttributesListBase):

View File

@ -70,8 +70,16 @@ class POINTCLOUD_UL_attributes(UIList):
flags = []
indices = [i for i in range(len(attributes))]
for item in attributes:
flags.append(0 if item.is_internal else self.bitflag_filter_item)
# Filtering by name
if self.filter_name:
flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert)
if not flags:
flags = [self.bitflag_filter_item] * len(attributes)
# Filtering internal attributes
for idx, item in enumerate(attributes):
flags[idx] = 0 if item.is_internal else flags[idx]
return flags, indices

View File

@ -68,7 +68,7 @@ class WORKSPACE_PT_addons(WorkSpaceButtonsPanel, Panel):
row.operator(
"wm.owner_disable" if is_enabled else "wm.owner_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT',
text=iface_("%s: %s" ) % (iface_(info["category"]), iface_(info["name"])),
text=iface_("%s: %s") % (iface_(info["category"]), iface_(info["name"])),
translate=False,
emboss=False,
).owner_id = module_name

View File

@ -307,8 +307,10 @@ class NODE_MT_select(Menu):
class NODE_MT_node(Menu):
bl_label = "Node"
def draw(self, _context):
def draw(self, context):
layout = self.layout
snode = context.space_data
is_compositor = snode.tree_type == 'CompositorNodeTree'
layout.operator("transform.translate")
layout.operator("transform.rotate")
@ -346,14 +348,16 @@ class NODE_MT_node(Menu):
layout.operator("node.hide_toggle")
layout.operator("node.mute_toggle")
layout.operator("node.preview_toggle")
if is_compositor:
layout.operator("node.preview_toggle")
layout.operator("node.hide_socket_toggle")
layout.operator("node.options_toggle")
layout.operator("node.collapse_hide_unused_toggle")
layout.separator()
if is_compositor:
layout.separator()
layout.operator("node.read_viewlayers")
layout.operator("node.read_viewlayers")
class NODE_MT_view_pie(Menu):

View File

@ -285,7 +285,6 @@ template<typename T> class SimpleMixer {
*/
void set(const int64_t index, const T &value, const float weight = 1.0f)
{
BLI_assert(weight >= 0.0f);
buffer_[index] = value * weight;
total_weights_[index] = weight;
}
@ -295,7 +294,6 @@ template<typename T> class SimpleMixer {
*/
void mix_in(const int64_t index, const T &value, const float weight = 1.0f)
{
BLI_assert(weight >= 0.0f);
buffer_[index] += value * weight;
total_weights_[index] += weight;
}

View File

@ -507,7 +507,7 @@ struct bNodeTree *ntreeFromID(struct ID *id);
void ntreeFreeLocalNode(struct bNodeTree *ntree, struct bNode *node);
void ntreeFreeLocalTree(struct bNodeTree *ntree);
struct bNode *ntreeFindType(const struct bNodeTree *ntree, int type);
struct bNode *ntreeFindType(struct bNodeTree *ntree, int type);
bool ntreeHasTree(const struct bNodeTree *ntree, const struct bNodeTree *lookup);
void ntreeUpdateAllNew(struct Main *main);
void ntreeUpdateAllUsers(struct Main *main, struct ID *id);
@ -629,7 +629,7 @@ const char *nodeStaticSocketLabel(int type, int subtype);
} \
((void)0)
struct bNodeSocket *nodeFindSocket(const struct bNode *node,
struct bNodeSocket *nodeFindSocket(struct bNode *node,
eNodeSocketInOut in_out,
const char *identifier);
struct bNodeSocket *nodeAddSocket(struct bNodeTree *ntree,
@ -670,6 +670,13 @@ void nodeUnlinkNode(struct bNodeTree *ntree, struct bNode *node);
void nodeUniqueName(struct bNodeTree *ntree, struct bNode *node);
void nodeUniqueID(struct bNodeTree *ntree, struct bNode *node);
/**
* Rebuild the `node_by_id` runtime vector set. Call after removing a node if not handled
* separately. This is important instead of just using `nodes_by_id.remove()` since it maintains
* the node order.
*/
void nodeRebuildIDVector(struct bNodeTree *node_tree);
/**
* Delete node, associated animation data and ID user count.
*/

View File

@ -212,9 +212,6 @@ class bNodeRuntime : NonCopyable, NonMovable {
/** #eNodeTreeChangedFlag. */
uint32_t changed_flag = 0;
/** For dependency and sorting. */
short done = 0;
/** Used as a boolean for execution. */
uint8_t need_exec = 0;

View File

@ -23,7 +23,6 @@ void ColorGeometry4fMixer::set(const int64_t index,
const ColorGeometry4f &color,
const float weight)
{
BLI_assert(weight >= 0.0f);
buffer_[index].r = color.r * weight;
buffer_[index].g = color.g * weight;
buffer_[index].b = color.b * weight;
@ -35,7 +34,6 @@ void ColorGeometry4fMixer::mix_in(const int64_t index,
const ColorGeometry4f &color,
const float weight)
{
BLI_assert(weight >= 0.0f);
ColorGeometry4f &output_color = buffer_[index];
output_color.r += color.r * weight;
output_color.g += color.g * weight;
@ -89,7 +87,6 @@ void ColorGeometry4bMixer::ColorGeometry4bMixer::set(int64_t index,
const ColorGeometry4b &color,
const float weight)
{
BLI_assert(weight >= 0.0f);
accumulation_buffer_[index][0] = color.r * weight;
accumulation_buffer_[index][1] = color.g * weight;
accumulation_buffer_[index][2] = color.b * weight;
@ -99,7 +96,6 @@ void ColorGeometry4bMixer::ColorGeometry4bMixer::set(int64_t index,
void ColorGeometry4bMixer::mix_in(int64_t index, const ColorGeometry4b &color, float weight)
{
BLI_assert(weight >= 0.0f);
float4 &accum_value = accumulation_buffer_[index];
accum_value[0] += color.r * weight;
accum_value[1] += color.g * weight;

View File

@ -242,8 +242,8 @@ static void movieclip_blend_write(BlendWriter *writer, ID *id, const void *id_ad
LISTBASE_FOREACH (MovieTrackingObject *, object, &tracking->objects) {
#if USE_LEGACY_CAMERA_OBJECT_FORMAT_ON_SAVE
/* When saving camers object in the legacy format clear the list of tracks. This is because the
* tracking object code is generic and assumes object owns the tracks in the list. For the
/* When saving cameras object in the legacy format clear the list of tracks. This is because
* the tracking object code is generic and assumes object owns the tracks in the list. For the
* camera tracks that is not the case in the legacy format. */
if (!is_undo && (object->flag & TRACKING_OBJECT_CAMERA)) {
MovieTrackingObject legacy_object = *object;

View File

@ -1455,7 +1455,7 @@ const char *nodeSocketTypeLabel(const bNodeSocketType *stype)
return stype->label[0] != '\0' ? stype->label : RNA_struct_ui_name(stype->ext_socket.srna);
}
bNodeSocket *nodeFindSocket(const bNode *node, eNodeSocketInOut in_out, const char *identifier)
bNodeSocket *nodeFindSocket(bNode *node, eNodeSocketInOut in_out, const char *identifier)
{
const ListBase *sockets = (in_out == SOCK_IN) ? &node->inputs : &node->outputs;
LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
@ -2933,12 +2933,12 @@ static void node_unlink_attached(bNodeTree *ntree, bNode *parent)
}
}
static void rebuild_nodes_vector(bNodeTree &node_tree)
void nodeRebuildIDVector(bNodeTree *node_tree)
{
/* Rebuild nodes #VectorSet which must have the same order as the list. */
node_tree.runtime->nodes_by_id.clear();
LISTBASE_FOREACH (bNode *, node, &node_tree.nodes) {
node_tree.runtime->nodes_by_id.add_new(node);
node_tree->runtime->nodes_by_id.clear();
LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) {
node_tree->runtime->nodes_by_id.add_new(node);
}
}
@ -2955,7 +2955,7 @@ static void node_free_node(bNodeTree *ntree, bNode *node)
if (ntree) {
BLI_remlink(&ntree->nodes, node);
/* Rebuild nodes #VectorSet which must have the same order as the list. */
rebuild_nodes_vector(*ntree);
nodeRebuildIDVector(ntree);
/* texture node has bad habit of keeping exec data around */
if (ntree->type == NTREE_TEXTURE && ntree->runtime->execdata) {
@ -3013,7 +3013,7 @@ void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node)
node_unlink_attached(ntree, node);
node_free_node(ntree, node);
rebuild_nodes_vector(*ntree);
nodeRebuildIDVector(ntree);
}
void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user)
@ -3073,7 +3073,7 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user)
/* Free node itself. */
node_free_node(ntree, node);
rebuild_nodes_vector(*ntree);
nodeRebuildIDVector(ntree);
}
static void node_socket_interface_free(bNodeTree * /*ntree*/,
@ -3437,7 +3437,7 @@ void ntreeRemoveSocketInterface(bNodeTree *ntree, bNodeSocket *sock)
/* ************ find stuff *************** */
bNode *ntreeFindType(const bNodeTree *ntree, int type)
bNode *ntreeFindType(bNodeTree *ntree, int type)
{
if (ntree) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {

View File

@ -499,7 +499,7 @@ static std::optional<UVBorderCorner> sharpest_border_corner(UVIsland &island)
/** The inner edge of a fan. */
struct InnerEdge {
MeshPrimitive *primitive;
/* UVs order are already applied. So uvs[0] mathes primitive->vertices[vert_order[0]]/ */
/* UVs order are already applied. So `uvs[0]` matches `primitive->vertices[vert_order[0]]`. */
float2 uvs[3];
int vert_order[3];
@ -1198,7 +1198,7 @@ UVBorder UVPrimitive::extract_border() const
{
Vector<UVBorderEdge> border_edges;
for (UVEdge *edge : edges) {
/* TODO remove const cast. only needed for debugging atm. */
/* TODO remove const cast. only needed for debugging ATM. */
UVBorderEdge border_edge(edge, const_cast<UVPrimitive *>(this));
border_edges.append(border_edge);
}
@ -1227,9 +1227,6 @@ const UVVertex *UVBorderEdge::get_uv_vertex(int index) const
return edge->vertices[actual_index];
}
/**
* Get the uv vertex from the primitive that is not part of the edge.
*/
const UVVertex *UVBorderEdge::get_other_uv_vertex() const
{
return uv_primitive->get_other_uv_vertex(edge->vertices[0], edge->vertices[1]);

View File

@ -83,11 +83,11 @@ void tracking_set_marker_coords_from_tracking(int frame_width,
* Convert the lens principal point (optical center) between normalized and pixel spaces.
*
* The normalized space stores principal point relative to the frame center which has normalized
* princibal coordinate of (0, 0). The right top corder of the frame corresponds to a notmalized
* principal coordinate of (1, 1), and the left bottom cornder corresponds to coordinate of
* principal coordinate of (0, 0). The right top corner of the frame corresponds to a normalized
* principal coordinate of (1, 1), and the left bottom corner corresponds to coordinate of
* (-1, -1).
*
* The pixel space is measured in pixels, with the reference being the left bottom cornder of
* The pixel space is measured in pixels, with the reference being the left bottom corner of
* the frame.
*/
void tracking_principal_point_normalized_to_pixel(const float principal_point_normalized[2],

View File

@ -132,7 +132,7 @@ class AtomicDisjointSet {
/**
* Get an identifier for each id. This is deterministic and does not depend on the order of
* joins. The ids are ordered by their first occurence. Consequently, `result[0]` is always zero
* joins. The ids are ordered by their first occurrence. Consequently, `result[0]` is always zero
* (unless there are no elements).
*/
void calc_reduced_ids(MutableSpan<int> result) const;

View File

@ -36,9 +36,9 @@ uint BLI_ghashutil_ptrhash(const void *key)
size_t y = (size_t)key;
/* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
* excessive hash collisions for dicts and sets */
* excessive hash collisions for dictionaries and sets */
/* NOTE: Unlike Python 'sizeof(uint)' is used instead of 'sizeof(void *)',
/* NOTE: Unlike Python `sizeof(uint)` is used instead of `sizeof(void *)`,
* Otherwise casting to 'uint' ignores the upper bits on 64bit platforms. */
return (uint)(y >> 4) | ((uint)y << (sizeof(uint[8]) - 4));
}

View File

@ -17,14 +17,14 @@ AtomicDisjointSet::AtomicDisjointSet(const int size) : items_(size)
});
}
static void update_first_occurence(Map<int, int> &map, const int root, const int index)
static void update_first_occurrence(Map<int, int> &map, const int root, const int index)
{
map.add_or_modify(
root,
[&](int *first_occurence) { *first_occurence = index; },
[&](int *first_occurence) {
if (index < *first_occurence) {
*first_occurence = index;
[&](int *first_occurrence) { *first_occurrence = index; },
[&](int *first_occurrence) {
if (index < *first_occurrence) {
*first_occurrence = index;
}
});
}
@ -37,49 +37,49 @@ void AtomicDisjointSet::calc_reduced_ids(MutableSpan<int> result) const
/* Find the root for element. With multi-threading, this root is not deterministic. So
* some postprocessing has to be done to make it deterministic. */
threading::EnumerableThreadSpecific<Map<int, int>> first_occurence_by_root_per_thread;
threading::EnumerableThreadSpecific<Map<int, int>> first_occurrence_by_root_per_thread;
threading::parallel_for(IndexRange(size), 1024, [&](const IndexRange range) {
Map<int, int> &first_occurence_by_root = first_occurence_by_root_per_thread.local();
Map<int, int> &first_occurrence_by_root = first_occurrence_by_root_per_thread.local();
for (const int i : range) {
const int root = this->find_root(i);
result[i] = root;
update_first_occurence(first_occurence_by_root, root, i);
update_first_occurrence(first_occurrence_by_root, root, i);
}
});
/* Build a map that contains the first element index that has a certain root. */
Map<int, int> &combined_map = first_occurence_by_root_per_thread.local();
for (const Map<int, int> &other_map : first_occurence_by_root_per_thread) {
Map<int, int> &combined_map = first_occurrence_by_root_per_thread.local();
for (const Map<int, int> &other_map : first_occurrence_by_root_per_thread) {
if (&combined_map == &other_map) {
continue;
}
for (const auto item : other_map.items()) {
update_first_occurence(combined_map, item.key, item.value);
update_first_occurrence(combined_map, item.key, item.value);
}
}
struct RootOccurence {
int root;
int first_occurence;
int first_occurrence;
};
/* Sort roots by first occurence. This removes the non-determinism above. */
Vector<RootOccurence, 16> root_occurences;
root_occurences.reserve(combined_map.size());
/* Sort roots by first occurrence. This removes the non-determinism above. */
Vector<RootOccurence, 16> root_occurrences;
root_occurrences.reserve(combined_map.size());
for (const auto item : combined_map.items()) {
root_occurences.append({item.key, item.value});
root_occurrences.append({item.key, item.value});
}
parallel_sort(root_occurences.begin(),
root_occurences.end(),
parallel_sort(root_occurrences.begin(),
root_occurrences.end(),
[](const RootOccurence &a, const RootOccurence &b) {
return a.first_occurence < b.first_occurence;
return a.first_occurrence < b.first_occurrence;
});
/* Remap original root values with deterministic values. */
Map<int, int> id_by_root;
id_by_root.reserve(root_occurences.size());
for (const int i : root_occurences.index_range()) {
id_by_root.add_new(root_occurences[i].root, i);
id_by_root.reserve(root_occurrences.size());
for (const int i : root_occurrences.index_range()) {
id_by_root.add_new(root_occurrences[i].root, i);
}
threading::parallel_for(IndexRange(size), 1024, [&](const IndexRange range) {
for (const int i : range) {

View File

@ -51,6 +51,23 @@ static bool BLI_path_is_abs(const char *name);
// #define DEBUG_STRSIZE
/**
* On UNIX it only makes sense to treat `/` as a path separator.
* On WIN32 either may be used.
*/
static bool is_sep_native_compat(const char ch)
{
if (ch == SEP) {
return true;
}
#ifdef WIN32
if (ch == ALTSEP) {
return true;
}
#endif
return false;
}
/* implementation */
int BLI_path_sequence_decode(const char *string, char *head, char *tail, ushort *r_digits_len)
@ -1450,7 +1467,7 @@ size_t BLI_path_append(char *__restrict dst, const size_t maxlen, const char *__
size_t dirlen = BLI_strnlen(dst, maxlen);
/* Inline #BLI_path_slash_ensure. */
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
if ((dirlen > 0) && !is_sep_native_compat(dst[dirlen - 1])) {
dst[dirlen++] = SEP;
dst[dirlen] = '\0';
}
@ -1467,7 +1484,7 @@ size_t BLI_path_append_dir(char *__restrict dst, const size_t maxlen, const char
size_t dirlen = BLI_path_append(dst, maxlen, dir);
if (dirlen + 1 < maxlen) {
/* Inline #BLI_path_slash_ensure. */
if ((dirlen > 0) && (dst[dirlen - 1] != SEP)) {
if ((dirlen > 0) && !is_sep_native_compat(dst[dirlen - 1])) {
dst[dirlen++] = SEP;
dst[dirlen] = '\0';
}
@ -1522,7 +1539,7 @@ size_t BLI_path_join_array(char *__restrict dst,
bool has_trailing_slash = false;
if (ofs != 0) {
size_t len = ofs;
while ((len != 0) && (path[len - 1] == SEP)) {
while ((len != 0) && is_sep_native_compat(path[len - 1])) {
len -= 1;
}
@ -1536,18 +1553,18 @@ size_t BLI_path_join_array(char *__restrict dst,
path = path_array[path_index];
has_trailing_slash = false;
const char *path_init = path;
while (path[0] == SEP) {
while (is_sep_native_compat(path[0])) {
path++;
}
size_t len = strlen(path);
if (len != 0) {
while ((len != 0) && (path[len - 1] == SEP)) {
while ((len != 0) && is_sep_native_compat(path[len - 1])) {
len -= 1;
}
if (len != 0) {
/* the very first path may have a slash at the end */
if (ofs && (dst[ofs - 1] != SEP)) {
if (ofs && !is_sep_native_compat(dst[ofs - 1])) {
dst[ofs++] = SEP;
if (ofs == dst_last) {
break;
@ -1570,7 +1587,7 @@ size_t BLI_path_join_array(char *__restrict dst,
}
if (has_trailing_slash) {
if ((ofs != dst_last) && (ofs != 0) && (dst[ofs - 1] != SEP)) {
if ((ofs != dst_last) && (ofs != 0) && !is_sep_native_compat(dst[ofs - 1])) {
dst[ofs++] = SEP;
}
}
@ -1598,7 +1615,7 @@ static bool path_name_at_index_forward(const char *__restrict path,
int i = 0;
while (true) {
const char c = path[i];
if (ELEM(c, SEP, '\0')) {
if ((c == '\0') || is_sep_native_compat(c)) {
if (prev + 1 != i) {
prev += 1;
/* Skip '/./' (behave as if they don't exist). */
@ -1633,7 +1650,7 @@ static bool path_name_at_index_backward(const char *__restrict path,
int i = prev - 1;
while (true) {
const char c = i >= 0 ? path[i] : '\0';
if (ELEM(c, SEP, '\0')) {
if ((c == '\0') || is_sep_native_compat(c)) {
if (prev - 1 != i) {
i += 1;
/* Skip '/./' (behave as if they don't exist). */
@ -1732,7 +1749,7 @@ int BLI_path_slash_ensure(char *string, size_t string_maxlen)
{
int len = strlen(string);
BLI_assert(len < string_maxlen);
if (len == 0 || string[len - 1] != SEP) {
if (len == 0 || !is_sep_native_compat(string[len - 1])) {
/* Avoid unlikely buffer overflow. */
if (len + 1 < string_maxlen) {
string[len] = SEP;
@ -1747,7 +1764,7 @@ void BLI_path_slash_rstrip(char *string)
{
int len = strlen(string);
while (len) {
if (string[len - 1] == SEP) {
if (is_sep_native_compat(string[len - 1])) {
string[len - 1] = '\0';
len--;
}

View File

@ -2001,6 +2001,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain)
*/
link = MEM_callocN(sizeof(bNodeLink), "link");
BLI_addtail(&ntree->links, link);
nodeUniqueID(ntree, node);
link->fromnode = NULL;
link->fromsock = gsock;
link->tonode = node;
@ -2024,6 +2025,7 @@ void blo_do_versions_250(FileData *fd, Library *lib, Main *bmain)
*/
link = MEM_callocN(sizeof(bNodeLink), "link");
BLI_addtail(&ntree->links, link);
nodeUniqueID(ntree, node);
link->fromnode = node;
link->fromsock = sock;
link->tonode = NULL;

View File

@ -581,9 +581,9 @@ static BMVert **bm_to_mesh_vertex_map(BMesh *bm, int ototvert)
* Also not correct but it's better then having it zeroed for e.g.
*
* - Missing key-index layer.
* In this case the basis key wont apply it's deltas to other keys and in the case
* a shape-key layer is missing, its coordinates will be initialized from the edit-mesh
* vertex locations instead of attempting to remap the shape-keys coordinates.
* In this case the basis key won't apply its deltas to other keys and if a shape-key layer is
* missing, its coordinates will be initialized from the edit-mesh vertex locations instead of
* attempting to remap the shape-keys coordinates.
*
* \note These cases are considered abnormal and shouldn't occur in typical usage.
* A warning is logged in this case to help troubleshooting bugs with shape-keys.

View File

@ -20,7 +20,7 @@ class NodeOperationBuilder;
/**
* \brief Wraps a bNode in its Node instance.
*
* For all nodetypes a wrapper class is created.
* For all node-types a wrapper class is created.
*
* \note When adding a new node to blender, this method needs to be changed to return the correct
* Node instance.

View File

@ -132,7 +132,7 @@ static GPUVertCompType get_comp_type_for_type(eCustomDataType type)
return GPU_COMP_I32;
case CD_PROP_BYTE_COLOR:
/* This should be u8,
* but u16 is required to store the color in linear space without precission loss */
* but u16 is required to store the color in linear space without precision loss */
return GPU_COMP_U16;
default:
return GPU_COMP_F32;

View File

@ -34,6 +34,7 @@
#include "UI_resources.h"
#include "ED_geometry.h"
#include "ED_mesh.h"
#include "ED_object.h"
#include "geometry_intern.hh"
@ -559,6 +560,28 @@ void GEOMETRY_OT_color_attribute_duplicate(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int geometry_attribute_convert_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
Object *ob = ED_object_context(C);
Mesh *mesh = static_cast<Mesh *>(ob->data);
const bke::AttributeMetaData meta_data = *mesh->attributes().lookup_meta_data(
BKE_id_attributes_active_get(&mesh->id)->name);
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "domain");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_enum_set(op->ptr, "domain", meta_data.domain);
}
prop = RNA_struct_find_property(op->ptr, "data_type");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_enum_set(op->ptr, "data_type", meta_data.data_type);
}
return WM_operator_props_dialog_popup(C, op, 300);
}
static void geometry_attribute_convert_ui(bContext * /*C*/, wmOperator *op)
{
uiLayout *layout = op->layout;
@ -576,13 +599,6 @@ static void geometry_attribute_convert_ui(bContext * /*C*/, wmOperator *op)
}
}
static int geometry_attribute_convert_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
return WM_operator_props_dialog_popup(C, op, 300);
}
static bool geometry_color_attribute_convert_poll(bContext *C)
{
if (!geometry_attributes_poll(C)) {
@ -614,6 +630,28 @@ static int geometry_color_attribute_convert_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static int geometry_color_attribute_convert_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
Object *ob = ED_object_context(C);
Mesh *mesh = static_cast<Mesh *>(ob->data);
const bke::AttributeMetaData meta_data = *mesh->attributes().lookup_meta_data(
BKE_id_attributes_active_color_get(&mesh->id)->name);
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "domain");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_enum_set(op->ptr, "domain", meta_data.domain);
}
prop = RNA_struct_find_property(op->ptr, "data_type");
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_enum_set(op->ptr, "data_type", meta_data.data_type);
}
return WM_operator_props_dialog_popup(C, op, 300);
}
static void geometry_color_attribute_convert_ui(bContext *UNUSED(C), wmOperator *op)
{
uiLayout *layout = op->layout;
@ -630,7 +668,7 @@ void GEOMETRY_OT_color_attribute_convert(wmOperatorType *ot)
ot->description = "Change how the color attribute is stored";
ot->idname = "GEOMETRY_OT_color_attribute_convert";
ot->invoke = geometry_attribute_convert_invoke;
ot->invoke = geometry_color_attribute_convert_invoke;
ot->exec = geometry_color_attribute_convert_exec;
ot->poll = geometry_color_attribute_convert_poll;
ot->ui = geometry_color_attribute_convert_ui;

View File

@ -401,7 +401,7 @@ static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchIt
name_ui,
id,
iconid,
has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0,
has_sep_char ? int(UI_BUT_HAS_SEP_CHAR) : 0,
name_prefix_offset)) {
return false;
}

View File

@ -4812,19 +4812,28 @@ bScreen *ED_screen_animation_no_scrub(const wmWindowManager *wm)
int ED_screen_animation_play(bContext *C, int sync, int mode)
{
bScreen *screen = CTX_wm_screen(C);
Scene *scene = CTX_data_scene(C);
Scene *scene_eval = DEG_get_evaluated_scene(CTX_data_ensure_evaluated_depsgraph(C));
if (ED_screen_animation_playing(CTX_wm_manager(C))) {
/* stop playback now */
ED_screen_animation_timer(C, 0, 0, 0);
BKE_sound_stop_scene(scene_eval);
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
Main *bmain = CTX_data_main(C);
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
LISTBASE_FOREACH (ViewLayer *, view_layer, &scene->view_layers) {
Depsgraph *graph = BKE_scene_get_depsgraph(scene, view_layer);
if (graph) {
Scene *scene_eval = DEG_get_evaluated_scene(graph);
/* The audio handles are preserved throughout the dependency graph evaluation.
* Checking for scene->playback_handle even for non-evaluated scene should be okay. */
BKE_sound_stop_scene(scene_eval);
WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
}
}
}
}
else {
/* these settings are currently only available from a menu in the TimeLine */
if (mode == 1) { /* XXX only play audio forwards!? */
Scene *scene_eval = DEG_get_evaluated_scene(CTX_data_ensure_evaluated_depsgraph(C));
BKE_sound_play_scene(scene_eval);
}

View File

@ -1400,7 +1400,7 @@ static void insert_seam_vert_array(const ProjPaintState *ps,
vec[1] *= ibuf_y;
vseam->angle = atan2f(vec[1], vec[0]);
/* If face windings are not initialized, something must be wrong. */
/* If the face winding data is not initialized, something must be wrong. */
BLI_assert((ps->faceWindingFlags[tri_index] & PROJ_FACE_WINDING_INIT) != 0);
vseam->normal_cw = (ps->faceWindingFlags[tri_index] & PROJ_FACE_WINDING_CW);

View File

@ -5854,7 +5854,7 @@ void SCULPT_OT_brush_stroke(wmOperatorType *ot)
ot->ui = sculpt_redo_empty_ui;
/* Flags (sculpt does own undo? (ton)). */
ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO;
ot->flag = OPTYPE_BLOCKING;
/* Properties. */

View File

@ -508,9 +508,8 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
float mask = 1.0f;
/* Since brush normal mode depends on the current mirror symmery pass
* it is not folded into the factor cache (when it exists).
*/
/* Since brush normal mode depends on the current mirror symmetry pass
* it is not folded into the factor cache (when it exists). */
if ((ss->cache || ss->filter_cache) &&
(automasking->settings.flags & BRUSH_AUTOMASKING_BRUSH_NORMAL)) {
mask *= automasking_brush_normal_factor(automasking, ss, vert, automask_data);

View File

@ -85,7 +85,7 @@ typedef struct PointTrackPick {
} PointTrackPick;
/* Pick point track which is closest to the given coordinate.
* Operates in the original non-stabilized and non-un-distored coordinates. */
* Operates in the original non-stabilized and non-un-distorted coordinates. */
PointTrackPick ed_tracking_pick_point_track(const TrackPickOptions *options,
struct bContext *C,
const float co[2]);
@ -114,7 +114,7 @@ typedef struct PlaneTrackPick {
} PlaneTrackPick;
/* Pick plane track which is closest to the given coordinate.
* Operates in the original non-stabilized and non-un-distored coordinates. */
* Operates in the original non-stabilized and non-un-distorted coordinates. */
PlaneTrackPick ed_tracking_pick_plane_track(const TrackPickOptions *options,
struct bContext *C,
const float co[2]);
@ -136,7 +136,7 @@ typedef struct TrackingPick {
} TrackingPick;
/* Pick closest point or plane track (whichever is the closest to the given coordinate).
* Operates in the original non-stabilized and non-un-distored coordinates. */
* Operates in the original non-stabilized and non-un-distorted coordinates. */
TrackingPick ed_tracking_pick_closest(const TrackPickOptions *options,
bContext *C,
const float co[2]);

View File

@ -277,7 +277,7 @@ PointTrackPick ed_tracking_pick_point_track(const TrackPickOptions *options,
/* Here we also check whether the mouse is actually closer to the widget which controls scale
* and tilt.
* NOTE: The tilt contorl is only visible for selected tracks. */
* NOTE: The tilt control is only visible for selected tracks. */
if (is_track_selected) {
distance_squared = mouse_to_tilt_distance_squared(marker, co, width, height);
if (distance_squared < current_pick.distance_px_squared) {

View File

@ -1709,7 +1709,7 @@ void NODE_OT_preview_toggle(wmOperatorType *ot)
/* callbacks */
ot->exec = node_preview_toggle_exec;
ot->poll = ED_operator_node_active;
ot->poll = composite_node_active;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -2795,7 +2795,7 @@ static bool node_shader_script_update_text_recursive(RenderEngine *engine,
RenderEngineType *type,
bNodeTree *ntree,
Text *text,
Set<bNodeTree *> &done_trees)
VectorSet<bNodeTree *> &done_trees)
{
bool found = false;
@ -2855,7 +2855,7 @@ static int node_shader_script_update_exec(bContext *C, wmOperator *op)
if (text) {
Set<bNodeTree *> done_trees;
VectorSet<bNodeTree *> done_trees;
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_SHADER) {

View File

@ -269,6 +269,7 @@ static bool node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode)
node->flag |= NODE_SELECT;
}
wgroup->runtime->nodes_by_id.clear();
bNodeLink *glinks_first = (bNodeLink *)ntree->links.last;
@ -446,30 +447,24 @@ static bool node_group_separate_selected(
ListBase anim_basepaths = {nullptr, nullptr};
Map<const bNode *, bNode *> node_map;
Map<const bNodeSocket *, bNodeSocket *> socket_map;
/* add selected nodes into the ntree */
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ngroup.nodes) {
if (!(node->flag & NODE_SELECT)) {
continue;
}
/* ignore interface nodes */
if (ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
nodeSetSelected(node, false);
continue;
}
/* Add selected nodes into the ntree, ignoring interface nodes. */
VectorSet<bNode *> nodes_to_move = get_selected_nodes(ngroup);
nodes_to_move.remove_if(
[](const bNode *node) { return node->is_group_input() || node->is_group_output(); });
for (bNode *node : nodes_to_move) {
bNode *newnode;
if (make_copy) {
/* make a copy */
newnode = bke::node_copy_with_mapping(&ngroup, *node, LIB_ID_COPY_DEFAULT, true, socket_map);
node_map.add_new(node, newnode);
newnode = bke::node_copy_with_mapping(&ntree, *node, LIB_ID_COPY_DEFAULT, true, socket_map);
}
else {
/* use the existing node */
newnode = node;
BLI_remlink(&ngroup.nodes, newnode);
BLI_addtail(&ntree.nodes, newnode);
nodeUniqueID(&ntree, newnode);
nodeUniqueName(&ntree, newnode);
}
/* Keep track of this node's RNA "base" path (the part of the path identifying the node)
@ -491,17 +486,14 @@ static bool node_group_separate_selected(
nodeDetachNode(&ngroup, newnode);
}
/* migrate node */
BLI_remlink(&ngroup.nodes, newnode);
BLI_addtail(&ntree.nodes, newnode);
nodeUniqueID(&ntree, newnode);
nodeUniqueName(&ntree, newnode);
if (!newnode->parent) {
newnode->locx += offset.x;
newnode->locy += offset.y;
}
}
if (!make_copy) {
nodeRebuildIDVector(&ngroup);
}
/* add internal links to the ntree */
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ngroup.links) {
@ -512,9 +504,9 @@ static bool node_group_separate_selected(
/* make a copy of internal links */
if (fromselect && toselect) {
nodeAddLink(&ntree,
node_map.lookup(link->fromnode),
ntree.node_by_id(link->fromnode->identifier),
socket_map.lookup(link->fromsock),
node_map.lookup(link->tonode),
ntree.node_by_id(link->tonode->identifier),
socket_map.lookup(link->tosock));
}
}
@ -642,97 +634,97 @@ void NODE_OT_group_separate(wmOperatorType *ot)
/** \name Make Group Operator
* \{ */
static bool node_group_make_use_node(const bNode &node, bNode *gnode)
static VectorSet<bNode *> get_nodes_to_group(bNodeTree &node_tree, bNode *group_node)
{
return (&node != gnode && !ELEM(node.type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT) &&
(node.flag & NODE_SELECT));
VectorSet<bNode *> nodes_to_group = get_selected_nodes(node_tree);
nodes_to_group.remove_if(
[](bNode *node) { return node->is_group_input() || node->is_group_output(); });
nodes_to_group.remove(group_node);
return nodes_to_group;
}
static bool node_group_make_test_selected(bNodeTree &ntree,
bNode *gnode,
const VectorSet<bNode *> &nodes_to_group,
const char *ntree_idname,
ReportList &reports)
{
int ok = true;
if (nodes_to_group.is_empty()) {
return false;
}
/* make a local pseudo node tree to pass to the node poll functions */
bNodeTree *ngroup = ntreeAddTree(nullptr, "Pseudo Node Group", ntree_idname);
BLI_SCOPED_DEFER([&]() {
ntreeFreeTree(ngroup);
MEM_freeN(ngroup);
});
/* check poll functions for selected nodes */
for (bNode *node : ntree.all_nodes()) {
if (node_group_make_use_node(*node, gnode)) {
const char *disabled_hint = nullptr;
if (node->typeinfo->poll_instance &&
!node->typeinfo->poll_instance(node, ngroup, &disabled_hint)) {
if (disabled_hint) {
BKE_reportf(&reports,
RPT_WARNING,
"Can not add node '%s' in a group:\n %s",
node->name,
disabled_hint);
}
else {
BKE_reportf(&reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
}
ok = false;
break;
for (bNode *node : nodes_to_group) {
const char *disabled_hint = nullptr;
if (node->typeinfo->poll_instance &&
!node->typeinfo->poll_instance(node, ngroup, &disabled_hint)) {
if (disabled_hint) {
BKE_reportf(&reports,
RPT_WARNING,
"Can not add node '%s' in a group:\n %s",
node->name,
disabled_hint);
}
else {
BKE_reportf(&reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
}
return false;
}
node->runtime->done = 0;
}
/* free local pseudo node tree again */
ntreeFreeTree(ngroup);
MEM_freeN(ngroup);
if (!ok) {
return false;
}
/* check if all connections are OK, no unselected node has both
* inputs and outputs to a selection */
LISTBASE_FOREACH (bNodeLink *, link, &ntree.links) {
if (node_group_make_use_node(*link->fromnode, gnode)) {
link->tonode->runtime->done |= 1;
}
if (node_group_make_use_node(*link->tonode, gnode)) {
link->fromnode->runtime->done |= 2;
}
}
ntree.ensure_topology_cache();
for (bNode *node : ntree.all_nodes()) {
if (!(node->flag & NODE_SELECT) && node != gnode && node->runtime->done == 3) {
if (nodes_to_group.contains(node)) {
continue;
}
auto sockets_connected_to_group = [&](const Span<bNodeSocket *> sockets) {
for (const bNodeSocket *socket : sockets) {
for (const bNodeSocket *other_socket : socket->directly_linked_sockets()) {
if (nodes_to_group.contains(const_cast<bNode *>(&other_socket->owner_node()))) {
return true;
}
}
}
return false;
};
if (sockets_connected_to_group(node->input_sockets()) &&
sockets_connected_to_group(node->output_sockets())) {
return false;
}
}
return true;
}
static int node_get_selected_minmax(
bNodeTree &ntree, bNode *gnode, float2 &min, float2 &max, bool use_size)
static void get_min_max_of_nodes(const Span<bNode *> nodes,
const bool use_size,
float2 &min,
float2 &max)
{
int totselect = 0;
if (nodes.is_empty()) {
min = float2(0);
max = float2(0);
return;
}
INIT_MINMAX2(min, max);
for (const bNode *node : ntree.all_nodes()) {
if (node_group_make_use_node(*node, gnode)) {
float2 loc;
nodeToView(node, node->offsetx, node->offsety, &loc.x, &loc.y);
for (const bNode *node : nodes) {
float2 loc;
nodeToView(node, node->offsetx, node->offsety, &loc.x, &loc.y);
math::min_max(loc, min, max);
if (use_size) {
loc.x += node->width;
loc.y -= node->height;
math::min_max(loc, min, max);
if (use_size) {
loc.x += node->width;
loc.y -= node->height;
math::min_max(loc, min, max);
}
totselect++;
}
}
/* sane min/max if no selected nodes */
if (totselect == 0) {
min[0] = min[1] = max[0] = max[1] = 0.0f;
}
return totselect;
}
/**
@ -803,11 +795,14 @@ static void node_group_make_redirect_incoming_link(
}
}
static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree, bNode *gnode)
static void node_group_make_insert_selected(const bContext &C,
bNodeTree &ntree,
bNode *gnode,
const VectorSet<bNode *> &nodes_to_move)
{
Main *bmain = CTX_data_main(&C);
bNodeTree *ngroup = (bNodeTree *)gnode->id;
bool expose_visible = false;
BLI_assert(!nodes_to_move.contains(gnode));
/* XXX rough guess, not nice but we don't have access to UI constants here ... */
static const float offsetx = 200;
@ -818,18 +813,16 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
nodeSetSelected(node, false);
}
/* auto-add interface for "solo" nodes */
const bool expose_visible = nodes_to_move.size() == 1;
float2 center, min, max;
const int totselect = node_get_selected_minmax(ntree, gnode, min, max, false);
get_min_max_of_nodes(nodes_to_move, false, min, max);
add_v2_v2v2(center, min, max);
mul_v2_fl(center, 0.5f);
float2 real_min, real_max;
node_get_selected_minmax(ntree, gnode, real_min, real_max, true);
/* auto-add interface for "solo" nodes */
if (totselect == 1) {
expose_visible = true;
}
get_min_max_of_nodes(nodes_to_move, true, real_min, real_max);
ListBase anim_basepaths = {nullptr, nullptr};
@ -839,44 +832,42 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
if (node->parent == nullptr) {
continue;
}
if (node_group_make_use_node(*node->parent, gnode) &&
!node_group_make_use_node(*node, gnode)) {
if (nodes_to_move.contains(node->parent) && nodes_to_move.contains(node)) {
nodeDetachNode(&ntree, node);
}
}
/* move nodes over */
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree.nodes) {
if (node_group_make_use_node(*node, gnode)) {
/* Keep track of this node's RNA "base" path (the part of the pat identifying the node)
* if the old node-tree has animation data which potentially covers this node. */
if (ntree.adt) {
PointerRNA ptr;
char *path;
for (bNode *node : nodes_to_move) {
/* Keep track of this node's RNA "base" path (the part of the pat identifying the node)
* if the old node-tree has animation data which potentially covers this node. */
if (ntree.adt) {
PointerRNA ptr;
char *path;
RNA_pointer_create(&ntree.id, &RNA_Node, node, &ptr);
path = RNA_path_from_ID_to_struct(&ptr);
RNA_pointer_create(&ntree.id, &RNA_Node, node, &ptr);
path = RNA_path_from_ID_to_struct(&ptr);
if (path) {
BLI_addtail(&anim_basepaths, animation_basepath_change_new(path, path));
}
if (path) {
BLI_addtail(&anim_basepaths, animation_basepath_change_new(path, path));
}
/* ensure valid parent pointers, detach if parent stays outside the group */
if (node->parent && !(node->parent->flag & NODE_SELECT)) {
nodeDetachNode(&ntree, node);
}
/* change node-collection membership */
BLI_remlink(&ntree.nodes, node);
BLI_addtail(&ngroup->nodes, node);
nodeUniqueID(ngroup, node);
nodeUniqueName(ngroup, node);
BKE_ntree_update_tag_node_removed(&ntree);
BKE_ntree_update_tag_node_new(ngroup, node);
}
/* ensure valid parent pointers, detach if parent stays outside the group */
if (node->parent && !(node->parent->flag & NODE_SELECT)) {
nodeDetachNode(&ntree, node);
}
/* change node-collection membership */
BLI_remlink(&ntree.nodes, node);
BLI_addtail(&ngroup->nodes, node);
nodeUniqueID(ngroup, node);
nodeUniqueName(ngroup, node);
BKE_ntree_update_tag_node_removed(&ntree);
BKE_ntree_update_tag_node_new(ngroup, node);
}
nodeRebuildIDVector(&ntree);
/* move animation data over */
if (ntree.adt) {
@ -904,8 +895,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
Map<bNodeSocket *, bNodeSocket *> reusable_sockets;
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
const bool fromselect = node_group_make_use_node(*link->fromnode, gnode);
const bool toselect = node_group_make_use_node(*link->tonode, gnode);
const bool fromselect = nodes_to_move.contains(link->fromnode);
const bool toselect = nodes_to_move.contains(link->tonode);
if ((fromselect && link->tonode == gnode) || (toselect && link->fromnode == gnode)) {
/* remove all links to/from the gnode.
@ -969,8 +960,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
/* move internal links */
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree.links) {
const bool fromselect = node_group_make_use_node(*link->fromnode, gnode);
const bool toselect = node_group_make_use_node(*link->tonode, gnode);
const bool fromselect = nodes_to_move.contains(link->fromnode);
const bool toselect = nodes_to_move.contains(link->tonode);
if (fromselect && toselect) {
BLI_remlink(&ntree.links, link);
@ -979,8 +970,8 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
}
/* move nodes in the group to the center */
for (bNode *node : ngroup->all_nodes()) {
if (node_group_make_use_node(*node, gnode) && !node->parent) {
for (bNode *node : nodes_to_move) {
if (!node->parent) {
node->locx -= center[0];
node->locy -= center[1];
}
@ -988,73 +979,67 @@ static void node_group_make_insert_selected(const bContext &C, bNodeTree &ntree,
/* Expose all unlinked sockets too but only the visible ones. */
if (expose_visible) {
for (bNode *node : ngroup->all_nodes()) {
if (node_group_make_use_node(*node, gnode)) {
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
bool skip = false;
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
if (link->tosock == sock) {
skip = true;
break;
}
}
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
for (bNode *node : nodes_to_move) {
LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) {
bool skip = false;
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
if (link->tosock == sock) {
skip = true;
break;
}
if (skip) {
continue;
}
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
node_group_input_update(ngroup, input_node);
/* create new internal link */
bNodeSocket *input_sock = node_group_input_find_socket(input_node, iosock->identifier);
nodeAddLink(ngroup, input_node, input_sock, node, sock);
}
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
skip = true;
}
if (skip) {
continue;
}
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
bool skip = false;
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
if (link->fromsock == sock) {
skip = true;
}
}
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
node_group_input_update(ngroup, input_node);
/* create new internal link */
bNodeSocket *input_sock = node_group_input_find_socket(input_node, iosock->identifier);
nodeAddLink(ngroup, input_node, input_sock, node, sock);
}
LISTBASE_FOREACH (bNodeSocket *, sock, &node->outputs) {
bool skip = false;
LISTBASE_FOREACH (bNodeLink *, link, &ngroup->links) {
if (link->fromsock == sock) {
skip = true;
}
if (skip) {
continue;
}
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
node_group_output_update(ngroup, output_node);
/* create new internal link */
bNodeSocket *output_sock = node_group_output_find_socket(output_node,
iosock->identifier);
nodeAddLink(ngroup, node, sock, output_node, output_sock);
}
if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
skip = true;
}
if (skip) {
continue;
}
bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
node_group_output_update(ngroup, output_node);
/* create new internal link */
bNodeSocket *output_sock = node_group_output_find_socket(output_node, iosock->identifier);
nodeAddLink(ngroup, node, sock, output_node, output_sock);
}
}
}
}
static bNode *node_group_make_from_selected(const bContext &C,
bNodeTree &ntree,
const char *ntype,
const char *ntreetype)
static bNode *node_group_make_from_nodes(const bContext &C,
bNodeTree &ntree,
const VectorSet<bNode *> &nodes_to_group,
const char *ntype,
const char *ntreetype)
{
Main *bmain = CTX_data_main(&C);
float2 min, max;
const int totselect = node_get_selected_minmax(ntree, nullptr, min, max, false);
/* don't make empty group */
if (totselect == 0) {
return nullptr;
}
get_min_max_of_nodes(nodes_to_group, false, min, max);
/* New node-tree. */
bNodeTree *ngroup = ntreeAddTree(bmain, "NodeGroup", ntreetype);
@ -1066,7 +1051,7 @@ static bNode *node_group_make_from_selected(const bContext &C,
gnode->locx = 0.5f * (min[0] + max[0]);
gnode->locy = 0.5f * (min[1] + max[1]);
node_group_make_insert_selected(C, ntree, gnode);
node_group_make_insert_selected(C, ntree, gnode, nodes_to_group);
return gnode;
}
@ -1081,11 +1066,12 @@ static int node_group_make_exec(bContext *C, wmOperator *op)
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
if (!node_group_make_test_selected(ntree, nullptr, ntree_idname, *op->reports)) {
VectorSet<bNode *> nodes_to_group = get_nodes_to_group(ntree, nullptr);
if (!node_group_make_test_selected(ntree, nodes_to_group, ntree_idname, *op->reports)) {
return OPERATOR_CANCELLED;
}
bNode *gnode = node_group_make_from_selected(*C, ntree, node_idname, ntree_idname);
bNode *gnode = node_group_make_from_nodes(*C, ntree, nodes_to_group, node_idname, ntree_idname);
if (gnode) {
bNodeTree *ngroup = (bNodeTree *)gnode->id;
@ -1137,17 +1123,17 @@ static int node_group_insert_exec(bContext *C, wmOperator *op)
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
bNode *gnode = node_group_get_active(C, node_idname);
if (!gnode || !gnode->id) {
return OPERATOR_CANCELLED;
}
bNodeTree *ngroup = (bNodeTree *)gnode->id;
if (!node_group_make_test_selected(*ntree, gnode, ngroup->idname, *op->reports)) {
VectorSet<bNode *> nodes_to_group = get_nodes_to_group(*ntree, gnode);
if (!node_group_make_test_selected(*ntree, nodes_to_group, ngroup->idname, *op->reports)) {
return OPERATOR_CANCELLED;
}
node_group_make_insert_selected(*C, *ntree, gnode);
node_group_make_insert_selected(*C, *ntree, gnode, nodes_to_group);
nodeSetActive(ntree, gnode);
ED_node_tree_push(snode, ngroup, gnode);

View File

@ -9,8 +9,8 @@
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "BKE_node.h"
@ -183,7 +183,7 @@ void node_keymap(wmKeyConfig *keyconf);
rctf node_frame_rect_inside(const bNode &node);
bool node_or_socket_isect_event(const bContext &C, const wmEvent &event);
Set<bNode *> get_selected_nodes(bNodeTree &node_tree);
VectorSet<bNode *> get_selected_nodes(bNodeTree &node_tree);
void node_deselect_all(SpaceNode &snode);
void node_socket_select(bNode *node, bNodeSocket &sock);
void node_socket_deselect(bNode *node, bNodeSocket &sock, bool deselect_node);

View File

@ -1630,40 +1630,42 @@ void NODE_OT_parent_set(wmOperatorType *ot)
/** \name Join Nodes Operator
* \{ */
/* tags for depth-first search */
#define NODE_JOIN_DONE 1
#define NODE_JOIN_IS_DESCENDANT 2
struct NodeJoinState {
bool done;
bool descendent;
};
static void node_join_attach_recursive(bNodeTree &ntree,
MutableSpan<NodeJoinState> join_states,
bNode *node,
bNode *frame,
const Set<bNode *> &selected_nodes)
const VectorSet<bNode *> &selected_nodes)
{
node->runtime->done |= NODE_JOIN_DONE;
join_states[node->runtime->index_in_tree].done = true;
if (node == frame) {
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
join_states[node->runtime->index_in_tree].descendent = true;
}
else if (node->parent) {
/* call recursively */
if (!(node->parent->runtime->done & NODE_JOIN_DONE)) {
node_join_attach_recursive(ntree, node->parent, frame, selected_nodes);
if (!join_states[node->parent->runtime->index_in_tree].done) {
node_join_attach_recursive(ntree, join_states, node->parent, frame, selected_nodes);
}
/* in any case: if the parent is a descendant, so is the child */
if (node->parent->runtime->done & NODE_JOIN_IS_DESCENDANT) {
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
if (join_states[node->parent->runtime->index_in_tree].descendent) {
join_states[node->runtime->index_in_tree].descendent = true;
}
else if (selected_nodes.contains(node)) {
/* if parent is not an descendant of the frame, reattach the node */
nodeDetachNode(&ntree, node);
nodeAttachNode(&ntree, node, frame);
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
join_states[node->runtime->index_in_tree].descendent = true;
}
}
else if (selected_nodes.contains(node)) {
nodeAttachNode(&ntree, node, frame);
node->runtime->done |= NODE_JOIN_IS_DESCENDANT;
join_states[node->runtime->index_in_tree].descendent = true;
}
}
@ -1673,19 +1675,16 @@ static int node_join_exec(bContext *C, wmOperator * /*op*/)
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &ntree = *snode.edittree;
const Set<bNode *> selected_nodes = get_selected_nodes(ntree);
const VectorSet<bNode *> selected_nodes = get_selected_nodes(ntree);
bNode *frame_node = nodeAddStaticNode(C, &ntree, NODE_FRAME);
nodeSetActive(&ntree, frame_node);
/* reset tags */
for (bNode *node : ntree.all_nodes()) {
node->runtime->done = 0;
}
Array<NodeJoinState> join_states(ntree.all_nodes().size(), NodeJoinState{false, false});
for (bNode *node : ntree.all_nodes()) {
if (!(node->runtime->done & NODE_JOIN_DONE)) {
node_join_attach_recursive(ntree, node, frame_node, selected_nodes);
if (!join_states[node->runtime->index_in_tree].done) {
node_join_attach_recursive(ntree, join_states, node, frame_node, selected_nodes);
}
}
@ -1808,32 +1807,35 @@ void NODE_OT_attach(wmOperatorType *ot)
/** \name Detach Operator
* \{ */
/* tags for depth-first search */
#define NODE_DETACH_DONE 1
#define NODE_DETACH_IS_DESCENDANT 2
struct NodeDetachstate {
bool done;
bool descendent;
};
static void node_detach_recursive(bNodeTree &ntree, bNode *node)
static void node_detach_recursive(bNodeTree &ntree,
MutableSpan<NodeDetachstate> detach_states,
bNode *node)
{
node->runtime->done |= NODE_DETACH_DONE;
detach_states[node->runtime->index_in_tree].done = true;
if (node->parent) {
/* call recursively */
if (!(node->parent->runtime->done & NODE_DETACH_DONE)) {
node_detach_recursive(ntree, node->parent);
if (!detach_states[node->parent->runtime->index_in_tree].done) {
node_detach_recursive(ntree, detach_states, node->parent);
}
/* in any case: if the parent is a descendant, so is the child */
if (node->parent->runtime->done & NODE_DETACH_IS_DESCENDANT) {
node->runtime->done |= NODE_DETACH_IS_DESCENDANT;
if (detach_states[node->parent->runtime->index_in_tree].descendent) {
detach_states[node->runtime->index_in_tree].descendent = true;
}
else if (node->flag & NODE_SELECT) {
/* if parent is not a descendant of a selected node, detach */
nodeDetachNode(&ntree, node);
node->runtime->done |= NODE_DETACH_IS_DESCENDANT;
detach_states[node->runtime->index_in_tree].descendent = true;
}
}
else if (node->flag & NODE_SELECT) {
node->runtime->done |= NODE_DETACH_IS_DESCENDANT;
detach_states[node->runtime->index_in_tree].descendent = true;
}
}
@ -1843,16 +1845,14 @@ static int node_detach_exec(bContext *C, wmOperator * /*op*/)
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &ntree = *snode.edittree;
/* reset tags */
for (bNode *node : ntree.all_nodes()) {
node->runtime->done = 0;
}
Array<NodeDetachstate> detach_states(ntree.all_nodes().size(), NodeDetachstate{false, false});
/* detach nodes recursively
* relative order is preserved here!
*/
for (bNode *node : ntree.all_nodes()) {
if (!(node->runtime->done & NODE_DETACH_DONE)) {
node_detach_recursive(ntree, node);
if (!detach_states[node->runtime->index_in_tree].done) {
node_detach_recursive(ntree, detach_states, node);
}
}

View File

@ -312,9 +312,9 @@ void node_deselect_all_output_sockets(SpaceNode &snode, const bool deselect_node
}
}
Set<bNode *> get_selected_nodes(bNodeTree &node_tree)
VectorSet<bNode *> get_selected_nodes(bNodeTree &node_tree)
{
Set<bNode *> selected_nodes;
VectorSet<bNode *> selected_nodes;
for (bNode *node : node_tree.all_nodes()) {
if (node->flag & NODE_SELECT) {
selected_nodes.add(node);
@ -1142,7 +1142,7 @@ static int node_select_linked_to_exec(bContext *C, wmOperator * /*op*/)
node_tree.ensure_topology_cache();
Set<bNode *> initial_selection = get_selected_nodes(node_tree);
VectorSet<bNode *> initial_selection = get_selected_nodes(node_tree);
for (bNode *node : initial_selection) {
for (bNodeSocket *output_socket : node->output_sockets()) {
@ -1192,7 +1192,7 @@ static int node_select_linked_from_exec(bContext *C, wmOperator * /*op*/)
node_tree.ensure_topology_cache();
Set<bNode *> initial_selection = get_selected_nodes(node_tree);
VectorSet<bNode *> initial_selection = get_selected_nodes(node_tree);
for (bNode *node : initial_selection) {
for (bNodeSocket *input_socket : node->input_sockets()) {

View File

@ -106,16 +106,22 @@ static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
break;
}
}
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
/* Note this does not remove auto-keys on locked cameras. */
copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
viewops_data_free(C, op->customdata);
op->customdata = NULL;
return OPERATOR_CANCELLED;
else if (event->type == vod->init.event_type) {
/* Check `vod->init.event_type` first in case RMB was used to invoke.
* in this case confirming takes precedence over canceling, see: T102937. */
if (event->val == KM_RELEASE) {
event_code = VIEW_CONFIRM;
}
}
else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
event_code = VIEW_CONFIRM;
else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
if (event->val == KM_PRESS) {
/* Note this does not remove auto-keys on locked cameras. */
copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
viewops_data_free(C, op->customdata);
op->customdata = NULL;
return OPERATOR_CANCELLED;
}
}
if (event_code == VIEW_APPLY) {

View File

@ -1231,6 +1231,12 @@ void transform_convert_mesh_mirrordata_calc(struct BMEditMesh *em,
* It can happen when vertices occupy the same position. */
continue;
}
if (vert_map[i].flag & flag) {
/* It's already a mirror.
* Avoid a mirror vertex dependency cycle.
* This can happen when the vertices are within the mirror threshold. */
continue;
}
vert_map[i_mirr] = (struct MirrorDataVert){i, flag};
mirror_elem_len++;

View File

@ -239,7 +239,7 @@ static bool uv_shear_in_clip_bounds_test(const TransInfo *t, const float value)
sub_v2_v2v2(uv, td->iloc, center);
uv[axis] = uv[axis] + value * uv[1 - axis] * (2 * axis - 1);
add_v2_v2(uv, center);
/* TODO: udim support. */
/* TODO: UDIM support. */
if (uv[axis] < 0.0f || 1.0f < uv[axis]) {
return false;
}

View File

@ -469,7 +469,9 @@ Vector<GVArray> evaluate_fields(ResourceScope &scope,
}
/* Still have to copy over the data in the destination provided by the caller. */
if (dst_varray.is_span()) {
array_utils::copy(computed_varray, mask, dst_varray.get_internal_span());
array_utils::copy(computed_varray,
mask,
dst_varray.get_internal_span().take_front(mask.min_array_size()));
}
else {
/* Slower materialize into a different structure. */

View File

@ -8,6 +8,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_boxpack_2d.h"
#include "BLI_convexhull_2d.h"
#include "BLI_ghash.h"
@ -16,6 +17,7 @@
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_rand.h"
#include "BLI_vector.hh"
#include "eigen_capi.h"
@ -1122,32 +1124,6 @@ static PFace *p_face_add_fill(ParamHandle *handle, PChart *chart, PVert *v1, PVe
return f;
}
static bool p_quad_split_direction(ParamHandle *handle, const float **co, const ParamKey *vkeys)
{
/* Slight bias to prefer one edge over the other in case they are equal, so
* that in symmetric models we choose the same split direction instead of
* depending on floating point errors to decide. */
float bias = 1.0f + 1e-6f;
float fac = len_v3v3(co[0], co[2]) * bias - len_v3v3(co[1], co[3]);
bool dir = (fac <= 0.0f);
/* The face exists check is there because of a special case:
* when two quads share three vertices, they can each be split into two triangles,
* resulting in two identical triangles. For example in Suzanne's nose. */
if (dir) {
if (p_face_exists(handle, vkeys, 0, 1, 2) || p_face_exists(handle, vkeys, 0, 2, 3)) {
return !dir;
}
}
else {
if (p_face_exists(handle, vkeys, 0, 1, 3) || p_face_exists(handle, vkeys, 1, 2, 3)) {
return !dir;
}
}
return dir;
}
/* Construction: boundary filling */
static void p_chart_boundaries(PChart *chart, PEdge **r_outer)
@ -3895,21 +3871,75 @@ void GEO_uv_parametrizer_face_add(ParamHandle *phandle,
BLI_assert(nverts >= 3);
param_assert(phandle->state == PHANDLE_STATE_ALLOCATED);
if (nverts > 4) {
if (nverts > 3) {
/* Protect against (manifold) geometry which has a non-manifold triangulation.
* See T102543. */
blender::Vector<int, 32> permute;
permute.reserve(nverts);
for (int i = 0; i < nverts; i++) {
permute.append_unchecked(i);
}
int i = nverts - 1;
while (i >= 0) {
/* Just check the "ears" of the n-gon.
* For quads, this is sufficient.
* For pents and higher, we might miss internal duplicate triangles, but note
* that such cases are rare if the source geometry is manifold and non-intersecting. */
int pm = permute.size();
BLI_assert(pm > 3);
int i0 = permute[i];
int i1 = permute[(i + 1) % pm];
int i2 = permute[(i + 2) % pm];
if (!p_face_exists(phandle, vkeys, i0, i1, i2)) {
i--; /* ...All good...*/
continue;
}
/* An existing triangle has already been inserted. As a heuristic, attempt to add the
* *previous* triangle. \note: Should probably call `GEO_uv_parametrizer_face_add` instead of
* `p_face_add_construct`. */
int iprev = permute[(i + pm - 1) % pm];
p_face_add_construct(phandle, key, vkeys, co, uv, iprev, i0, i1, pin, select);
permute.remove(i);
if (permute.size() == 3) {
break;
}
}
if (permute.size() != nverts) {
int pm = permute.size();
/* Add the remaining pm-gon. */
blender::Array<ParamKey> vkeys_sub(pm);
blender::Array<const float *> co_sub(pm);
blender::Array<float *> uv_sub(pm);
blender::Array<bool> pin_sub(pm);
blender::Array<bool> select_sub(pm);
for (int i = 0; i < pm; i++) {
int j = permute[i];
vkeys_sub[i] = vkeys[j];
co_sub[i] = co[j];
uv_sub[i] = uv[j];
pin_sub[i] = pin && pin[j];
select_sub[i] = select && select[j];
}
p_add_ngon(phandle,
key,
pm,
&vkeys_sub.first(),
&co_sub.first(),
&uv_sub.first(),
&pin_sub.first(),
&select_sub.first());
return; /* Nothing more to do. */
}
/* No "ears" have previously been inserted. Continue as normal. */
}
if (nverts > 3) {
/* ngon */
p_add_ngon(phandle, key, nverts, vkeys, co, uv, pin, select);
}
else if (nverts == 4) {
/* quad */
if (p_quad_split_direction(phandle, co, vkeys)) {
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select);
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 2, 3, pin, select);
}
else {
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 3, pin, select);
p_face_add_construct(phandle, key, vkeys, co, uv, 1, 2, 3, pin, select);
}
}
else if (!p_face_exists(phandle, vkeys, 0, 1, 2)) {
/* triangle */
p_face_add_construct(phandle, key, vkeys, co, uv, 0, 1, 2, pin, select);

View File

@ -847,7 +847,7 @@ void GPU_texture_get_mipmap_size(GPUTexture *tex, int lvl, int *r_size)
GPUPixelBuffer *GPU_pixel_buffer_create(uint size)
{
/* Ensure buffer satifies the alignment of 256 bytes for copying
/* Ensure buffer satisfies the alignment of 256 bytes for copying
* data between buffers and textures. As specified in:
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
*

View File

@ -88,7 +88,7 @@ class MTLStateManager : public StateManager {
class MTLFence : public Fence {
private:
/* Using an event in this instance, as this is global for the command stream, rather than being
* inserted at the encoder level. This has the behaviour to match the GL functionality. */
* inserted at the encoder level. This has the behavior to match the GL functionality. */
id<MTLEvent> mtl_event_ = nil;
/* Events can be re-used multiple times. We can track a counter flagging the latest value
* signalled. */

View File

@ -1860,7 +1860,7 @@ MTLPixelBuffer::MTLPixelBuffer(uint size) : PixelBuffer(size)
{
MTLContext *ctx = MTLContext::get();
BLI_assert(ctx);
/* Ensure buffer satifies the alignment of 256 bytes for copying
/* Ensure buffer satisfies the alignment of 256 bytes for copying
* data between buffers and textures. As specified in:
* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
BLI_assert(size >= 256);

View File

@ -9,7 +9,7 @@
namespace blender::gpu {
VKPixelBuffer::VKPixelBuffer(int64_t size): PixelBuffer(size)
VKPixelBuffer::VKPixelBuffer(int64_t size) : PixelBuffer(size)
{
}

View File

@ -47,7 +47,7 @@ static void copy_property_from_node(const eNodeSocketDatatype property_type,
if (!node) {
return;
}
bNodeSocket *socket{nodeFindSocket(node, SOCK_IN, identifier)};
const bNodeSocket *socket = nodeFindSocket(const_cast<bNode *>(node), SOCK_IN, identifier);
BLI_assert(socket && socket->type == property_type);
if (!socket) {
return;
@ -55,21 +55,21 @@ static void copy_property_from_node(const eNodeSocketDatatype property_type,
switch (property_type) {
case SOCK_FLOAT: {
BLI_assert(r_property.size() == 1);
bNodeSocketValueFloat *socket_def_value = static_cast<bNodeSocketValueFloat *>(
const bNodeSocketValueFloat *socket_def_value = static_cast<const bNodeSocketValueFloat *>(
socket->default_value);
r_property[0] = socket_def_value->value;
break;
}
case SOCK_RGBA: {
BLI_assert(r_property.size() == 3);
bNodeSocketValueRGBA *socket_def_value = static_cast<bNodeSocketValueRGBA *>(
const bNodeSocketValueRGBA *socket_def_value = static_cast<const bNodeSocketValueRGBA *>(
socket->default_value);
copy_v3_v3(r_property.data(), socket_def_value->value);
break;
}
case SOCK_VECTOR: {
BLI_assert(r_property.size() == 3);
bNodeSocketValueVector *socket_def_value = static_cast<bNodeSocketValueVector *>(
const bNodeSocketValueVector *socket_def_value = static_cast<const bNodeSocketValueVector *>(
socket->default_value);
copy_v3_v3(r_property.data(), socket_def_value->value);
break;

View File

@ -49,12 +49,14 @@ typedef struct MovieTrackingCamera {
short units;
char _pad1[2];
/* Principal point (optical center) stored in normalized coordinates.
/**
* Principal point (optical center) stored in normalized coordinates.
*
* The normalized space stores principal point relative to the frame center which has normalized
* princibal coordinate of (0, 0). The right top corder of the frame corresponds to a notmalized
* principal coordinate of (1, 1), and the left bottom cornder corresponds to coordinate of
* (-1, -1). */
* principal coordinate of (0, 0). The right top corer of the frame corresponds to a normalized
* principal coordinate of (1, 1), and the left bottom corner corresponds to coordinate of
* (-1, -1).
*/
float principal_point[2];
/** Legacy principal point in pixel space. */

View File

@ -1033,6 +1033,7 @@ static void rna_def_tex_slot(BlenderRNA *brna)
RNA_def_property_string_funcs(
prop, "rna_TexPaintSlot_name_get", "rna_TexPaintSlot_name_length", NULL);
RNA_def_property_ui_text(prop, "Name", "Name of the slot");
RNA_def_struct_name_property(srna, prop);
prop = RNA_def_property(srna, "icon_value", PROP_INT, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

@ -24,6 +24,8 @@
#include "node_common.h"
#include "node_geometry_register.hh"
bNodeTreeType *ntreeType_Geometry;
static void geometry_node_tree_get_from_context(

Some files were not shown because too many files have changed in this diff Show More