UI: Region polling support #105088

Merged
Julian Eisel merged 39 commits from JulianEisel/blender:temp-region-poll into main 2023-04-05 15:30:46 +02:00
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