Cycles: Make adaptive subdivision a non-experimental feature

* Add adaptive subdivision properties natively on the subdivision surface
  modifier, so that other engines may reuse them in the future. This also
  resolve issues where they would not get copied properly.
* Remove "Feature Set" option in the render properties, this was the last
  experimental one.
* Add space choice between "Pixel" and "Object". The latter is new and can
  be used for object space dicing that works with instances. Instead of
  a pixel size an object space edge length is specified.
* Add object space subdivision test.

Ref #53901

Pull Request: https://projects.blender.org/blender/blender/pulls/146723
This commit is contained in:
Brecht Van Lommel
2025-09-25 16:18:50 +02:00
committed by Brecht Van Lommel
parent 2a1a658492
commit 689f182792
24 changed files with 195 additions and 135 deletions

View File

@@ -33,17 +33,6 @@ enum_devices = (
"Use GPU compute device for rendering, configured in the system tab in the user preferences"),
)
enum_feature_set = (
('SUPPORTED',
"Supported",
"Only use finished and supported features"),
('EXPERIMENTAL',
"Experimental",
"Use experimental and incomplete features that might be broken or change in the future",
'ERROR',
1),
)
enum_bvh_layouts = (
('BVH2', "BVH2", "", 1),
('EMBREE', "Embree", "", 4),
@@ -395,13 +384,6 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
items=enum_devices,
default='CPU',
)
feature_set: EnumProperty(
name="Feature Set",
description="Feature set to use for rendering",
items=enum_feature_set,
default='SUPPORTED',
update=update_render_engine,
)
shading_system: BoolProperty(
name="Open Shading Language",
description="Use Open Shading Language",
@@ -806,22 +788,20 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
dicing_rate: FloatProperty(
name="Dicing Rate",
description="Size of a micropolygon in pixels",
description="Multiplier for per object adaptive subdivision size",
min=0.1, max=1000.0, soft_min=0.5,
default=1.0,
subtype='PIXEL'
)
preview_dicing_rate: FloatProperty(
name="Viewport Dicing Rate",
description="Size of a micropolygon in pixels during preview render",
description="Multiplier for per object adaptive subdivision size in the viewport",
min=0.1, max=1000.0, soft_min=0.5,
default=8.0,
subtype='PIXEL'
)
max_subdivisions: IntProperty(
name="Max Subdivisions",
description="Stop subdividing when this level is reached even if the dice rate would produce finer tessellation",
description="Stop subdividing when this level is reached even if the dicing rate would produce finer tessellation",
min=0,
max=16,
default=12,
@@ -1378,19 +1358,6 @@ class CyclesObjectSettings(bpy.types.PropertyGroup):
default=False,
)
use_adaptive_subdivision: BoolProperty(
name="Use Adaptive Subdivision",
description="Use adaptive render time subdivision",
default=False,
)
dicing_rate: FloatProperty(
name="Dicing Scale",
description="Multiplier for scene dicing rate (located in the Subdivision panel)",
min=0.1, max=1000.0, soft_min=0.5,
default=1.0,
)
shadow_terminator_offset: FloatProperty(
name="Shadow Terminator Shading Offset",
description="Push the shadow terminator towards the light to hide artifacts on low poly geometry",

View File

@@ -524,10 +524,6 @@ class CYCLES_RENDER_PT_subdivision(CyclesButtonsPanel, Panel):
bl_label = "Subdivision"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
return (context.scene.render.engine == 'CYCLES') and (context.scene.cycles.feature_set == 'EXPERIMENTAL')
def draw(self, context):
layout = self.layout
layout.use_property_split = True
@@ -2450,9 +2446,6 @@ def draw_device(self, context):
from . import engine
cscene = scene.cycles
col = layout.column()
col.prop(cscene, "feature_set")
col = layout.column()
col.active = show_device_active(context)
col.prop(cscene, "device")

View File

@@ -834,9 +834,20 @@ static void create_subd_mesh(Scene *scene,
}
/* Set subd parameters. */
PointerRNA cobj = RNA_pointer_get(&b_ob.ptr, "cycles");
const float subd_dicing_rate = max(0.1f, RNA_float_get(&cobj, "dicing_rate") * dicing_rate);
Mesh::SubdivisionAdaptiveSpace space = Mesh::SUBDIVISION_ADAPTIVE_SPACE_PIXEL;
switch (subsurf_mod.adaptive_space()) {
case BL::SubsurfModifier::adaptive_space_OBJECT:
space = Mesh::SUBDIVISION_ADAPTIVE_SPACE_OBJECT;
break;
case BL::SubsurfModifier::adaptive_space_PIXEL:
space = Mesh::SUBDIVISION_ADAPTIVE_SPACE_PIXEL;
break;
}
const float subd_dicing_rate = (space == Mesh::SUBDIVISION_ADAPTIVE_SPACE_PIXEL) ?
max(0.1f, subsurf_mod.adaptive_pixel_size() * dicing_rate) :
subsurf_mod.adaptive_object_edge_length() * dicing_rate;
mesh->set_subd_adaptive_space(space);
mesh->set_subd_dicing_rate(subd_dicing_rate);
mesh->set_subd_max_level(max_subdivisions);
mesh->set_subd_objecttoworld(get_transform(b_ob.matrix_world()));

View File

@@ -337,8 +337,7 @@ void BlenderSync::sync_integrator(BL::ViewLayer &b_view_layer,
PointerRNA cscene = RNA_pointer_get(&b_scene.ptr, "cycles");
/* No adaptive subdivision for baking, mesh needs to match Blender exactly. */
use_adaptive_subdivision = (get_enum(cscene, "feature_set") != 0) && !b_bake_target;
use_experimental_procedural = (get_enum(cscene, "feature_set") != 0);
use_adaptive_subdivision = !b_bake_target;
Integrator *integrator = scene->integrator;
@@ -936,9 +935,6 @@ SessionParams BlenderSync::get_session_params(BL::RenderEngine &b_engine,
params.temp_dir = b_engine.temporary_directory();
}
/* feature set */
params.experimental = (get_enum(cscene, "feature_set") != 0);
/* Headless and background rendering. */
params.headless = BlenderSession::headless;
params.background = background;

View File

@@ -241,7 +241,6 @@ class BlenderSync {
Scene *scene;
bool preview;
bool use_experimental_procedural = false;
bool use_adaptive_subdivision = false;
bool use_developer_ui;

View File

@@ -667,17 +667,15 @@ static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b
static BL::SubsurfModifier object_subdivision_modifier(BL::Object &b_ob, const bool preview)
{
PointerRNA cobj = RNA_pointer_get(&b_ob.ptr, "cycles");
if (cobj.data && !b_ob.modifiers.empty()) {
if (!b_ob.modifiers.empty()) {
BL::Modifier mod = b_ob.modifiers[b_ob.modifiers.length() - 1];
const bool enabled = preview ? mod.show_viewport() : mod.show_render();
if (enabled && mod.type() == BL::Modifier::type_SUBSURF &&
RNA_boolean_get(&cobj, "use_adaptive_subdivision"))
{
if (enabled && mod.type() == BL::Modifier::type_SUBSURF) {
BL::SubsurfModifier subsurf(mod);
return subsurf;
if (subsurf.use_adaptive_subdivision()) {
return subsurf;
}
}
}

View File

@@ -805,8 +805,10 @@ void GeometryManager::device_update(Device *device,
SubdParams subd_params(mesh);
subd_params.dicing_rate = mesh->get_subd_dicing_rate();
subd_params.max_level = mesh->get_subd_max_level();
subd_params.objecttoworld = mesh->get_subd_objecttoworld();
subd_params.camera = dicing_camera;
if (mesh->get_subd_adaptive_space() == Mesh::SUBDIVISION_ADAPTIVE_SPACE_PIXEL) {
subd_params.objecttoworld = mesh->get_subd_objecttoworld();
subd_params.camera = dicing_camera;
}
mesh->tessellate(subd_params);
}

View File

@@ -305,6 +305,14 @@ NODE_DEFINE(Mesh)
SOCKET_INT_ARRAY(subd_ptex_offset, "Subdivision Face PTex Offset", array<int>());
/* Subdivisions parameters */
static NodeEnum subd_adaptive_space_enum;
subd_adaptive_space_enum.insert("pixel", SUBDIVISION_ADAPTIVE_SPACE_PIXEL);
subd_adaptive_space_enum.insert("object", SUBDIVISION_ADAPTIVE_SPACE_OBJECT);
SOCKET_ENUM(subd_adaptive_space,
"Subdivision Adaptive Space",
subd_adaptive_space_enum,
SUBDIVISION_ADAPTIVE_SPACE_PIXEL);
SOCKET_FLOAT(subd_dicing_rate, "Subdivision Dicing Rate", 1.0f)
SOCKET_INT(subd_max_level, "Max Subdivision Level", 1);
SOCKET_TRANSFORM(subd_objecttoworld, "Subdivision Object Transform", transform_identity());
@@ -316,7 +324,8 @@ bool Mesh::need_tesselation()
{
return (subdivision_type != SUBDIVISION_NONE) &&
(verts_is_modified() || subd_dicing_rate_is_modified() ||
subd_objecttoworld_is_modified() || subd_max_level_is_modified());
subd_adaptive_space_is_modified() || subd_objecttoworld_is_modified() ||
subd_max_level_is_modified());
}
Mesh::Mesh(const NodeType *node_type, Type geom_type_)

View File

@@ -135,6 +135,11 @@ class Mesh : public Geometry {
SUBDIVISION_FVAR_LINEAR_ALL,
};
enum SubdivisionAdaptiveSpace {
SUBDIVISION_ADAPTIVE_SPACE_PIXEL,
SUBDIVISION_ADAPTIVE_SPACE_OBJECT,
};
NODE_SOCKET_API(SubdivisionType, subdivision_type)
NODE_SOCKET_API(SubdivisionBoundaryInterpolation, subdivision_boundary_interpolation)
NODE_SOCKET_API(SubdivisionFVarInterpolation, subdivision_fvar_interpolation)
@@ -161,6 +166,7 @@ class Mesh : public Geometry {
NODE_SOCKET_API_ARRAY(array<float>, subd_vert_creases_weight)
/* Subdivisions parameters */
NODE_SOCKET_API(SubdivisionAdaptiveSpace, subd_adaptive_space)
NODE_SOCKET_API(float, subd_dicing_rate)
NODE_SOCKET_API(int, subd_max_level)
NODE_SOCKET_API(Transform, subd_objecttoworld)

View File

@@ -44,7 +44,6 @@ class SessionParams {
bool headless;
bool background;
bool experimental;
int samples;
bool use_sample_subset;
int sample_subset_offset;
@@ -73,7 +72,6 @@ class SessionParams {
headless = false;
background = false;
experimental = false;
samples = 1024;
use_sample_subset = false;
sample_subset_offset = 0;
@@ -97,10 +95,10 @@ class SessionParams {
/* Modified means we have to recreate the session, any parameter changes
* that can be handled by an existing Session are omitted. */
return !(device == params.device && headless == params.headless &&
background == params.background && experimental == params.experimental &&
pixel_size == params.pixel_size && threads == params.threads &&
use_profiling == params.use_profiling && shadingsystem == params.shadingsystem &&
use_auto_tile == params.use_auto_tile && tile_size == params.tile_size);
background == params.background && pixel_size == params.pixel_size &&
threads == params.threads && use_profiling == params.use_profiling &&
shadingsystem == params.shadingsystem && use_auto_tile == params.use_auto_tile &&
tile_size == params.tile_size);
}
};

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 91
#define BLENDER_FILE_SUBVERSION 92
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -220,14 +220,6 @@ bool BKE_scene_uses_cycles(const Scene *scene);
bool BKE_scene_uses_shader_previews(const Scene *scene);
/**
* Return whether the Cycles experimental feature is enabled. It is invalid to call without first
* ensuring that Cycles is the active render engine (e.g. with #BKE_scene_uses_cycles).
*
* \note We cannot use `const` as RNA_id_pointer_create is not using a const ID.
*/
bool BKE_scene_uses_cycles_experimental_features(Scene *scene);
void BKE_scene_copy_data_eevee(Scene *sce_dst, const Scene *sce_src);
void BKE_scene_disable_color_management(Scene *scene);

View File

@@ -2801,20 +2801,6 @@ enum eCyclesFeatureSet {
CYCLES_FEATURES_EXPERIMENTAL = 1,
};
bool BKE_scene_uses_cycles_experimental_features(Scene *scene)
{
BLI_assert(BKE_scene_uses_cycles(scene));
PointerRNA scene_ptr = RNA_id_pointer_create(&scene->id);
PointerRNA cycles_ptr = RNA_pointer_get(&scene_ptr, "cycles");
if (RNA_pointer_is_null(&cycles_ptr)) {
/* The pointer only exists if Cycles is enabled. */
return false;
}
return RNA_enum_get(&cycles_ptr, "feature_set") == CYCLES_FEATURES_EXPERIMENTAL;
}
void BKE_scene_base_flag_to_objects(const Scene *scene, ViewLayer *view_layer)
{
BKE_view_layer_synced_ensure(scene, view_layer);

View File

@@ -2658,6 +2658,44 @@ static void sequencer_remove_listbase_pointers(Scene &scene)
blender::seq::meta_stack_set(&scene, last_meta_stack->parent_strip);
}
static void do_version_adaptive_subdivision(Main *bmain)
{
/* Move cycles properties natively into subdivision surface modifier. */
bool experimental_features = false;
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
IDProperty *idprop = version_cycles_properties_from_ID(&scene->id);
if (idprop) {
experimental_features |= version_cycles_property_boolean(idprop, "feature_set", false);
}
}
LISTBASE_FOREACH (Object *, object, &bmain->objects) {
bool use_adaptive_subdivision = false;
float dicing_rate = 1.0f;
IDProperty *idprop = version_cycles_properties_from_ID(&object->id);
if (idprop) {
if (experimental_features) {
use_adaptive_subdivision = version_cycles_property_boolean(
idprop, "use_adaptive_subdivision", false);
}
dicing_rate = version_cycles_property_float(idprop, "dicing_rate", 1.0f);
}
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Subsurf) {
SubsurfModifierData *smd = (SubsurfModifierData *)md;
if (use_adaptive_subdivision) {
smd->flags |= eSubsurfModifierFlag_UseAdaptiveSubdivision;
smd->adaptive_space = SUBSURF_ADAPTIVE_SPACE_PIXEL;
smd->adaptive_pixel_size = dicing_rate;
smd->adaptive_object_edge_length = 0.01f;
}
}
}
}
}
void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
{
using namespace blender;
@@ -3625,6 +3663,10 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 92)) {
do_version_adaptive_subdivision(bmain);
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -615,6 +615,9 @@
.uv_smooth = SUBSURF_UV_SMOOTH_PRESERVE_BOUNDARIES, \
.quality = 3, \
.boundary_smooth = SUBSURF_BOUNDARY_SMOOTH_ALL, \
.adaptive_space = SUBSURF_ADAPTIVE_SPACE_PIXEL, \
.adaptive_pixel_size = 1.0f, \
.adaptive_object_edge_length = 0.01f, \
}
#define _DNA_DEFAULT_SurfaceModifierData \

View File

@@ -234,8 +234,14 @@ typedef enum {
eSubsurfModifierFlag_UseCrease = (1 << 4),
eSubsurfModifierFlag_UseCustomNormals = (1 << 5),
eSubsurfModifierFlag_UseRecursiveSubdivision = (1 << 6),
eSubsurfModifierFlag_UseAdaptiveSubdivision = (1 << 7),
} SubsurfModifierFlag;
typedef enum {
SUBSURF_ADAPTIVE_SPACE_PIXEL = 0,
SUBSURF_ADAPTIVE_SPACE_OBJECT = 1,
} eSubsurfAdaptiveSpace;
typedef enum {
SUBSURF_TYPE_CATMULL_CLARK = 0,
SUBSURF_TYPE_SIMPLE = 1,
@@ -268,7 +274,11 @@ typedef struct SubsurfModifierData {
short quality;
/** #eSubsurfBoundarySmooth. */
short boundary_smooth;
char _pad[2];
/* Adaptive subdivision. */
/** #eSubsurfAdaptiveSpace */
short adaptive_space;
float adaptive_pixel_size;
float adaptive_object_edge_length;
} SubsurfModifierData;
typedef struct LatticeModifierData {

View File

@@ -2451,6 +2451,21 @@ static void rna_def_modifier_subsurf(BlenderRNA *brna)
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem prop_adaptive_space_items[] = {
{SUBSURF_ADAPTIVE_SPACE_PIXEL,
"PIXEL",
0,
"Pixel",
"Subdivide polygons to reach a specified pixel size on screen"},
{SUBSURF_ADAPTIVE_SPACE_OBJECT,
"OBJECT",
0,
"Object",
"Subdivide to reach a specified edge length in object space. This is required to use "
"adaptive subdivision for instanced meshes"},
{0, nullptr, 0, nullptr, nullptr},
};
StructRNA *srna;
PropertyRNA *prop;
@@ -2510,6 +2525,32 @@ static void rna_def_modifier_subsurf(BlenderRNA *brna)
"levels of subdivision (smoothest possible shape)");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_adaptive_subdivision", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "flags", eSubsurfModifierFlag_UseAdaptiveSubdivision);
RNA_def_property_ui_text(
prop, "Use Adaptive Subdivision", "Adaptively subdivide mesh based on camera distance");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "adaptive_space", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_adaptive_space_items);
RNA_def_property_ui_text(prop, "Adaptive Space", "How to adaptively subdivide the mesh");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "adaptive_pixel_size", PROP_FLOAT, PROP_PIXEL);
RNA_def_property_ui_text(
prop, "Pixel Size", "Target polygon pixel size for adaptive subdivision");
RNA_def_property_range(prop, 0.1f, 1000.0f);
RNA_def_property_ui_range(prop, 0.5f, 1000.0f, 10, 3);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "adaptive_object_edge_length", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_ui_text(
prop, "Edge Length", "Target object space edge length for adaptive subdivision");
RNA_def_property_range(prop, 0.0001f, 1000.0f);
RNA_def_property_ui_range(prop, 0.001f, 1000.0f, 10, 3);
RNA_def_property_update(prop, 0, "rna_Modifier_update");
rna_def_modifier_panel_open_prop(srna, "open_adaptive_subdivision_panel", 0);
rna_def_modifier_panel_open_prop(srna, "open_advanced_panel", 1);

View File

@@ -19,6 +19,7 @@
#include "DNA_defaults.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
@@ -315,7 +316,6 @@ static void deform_matrices(ModifierData *md,
}
}
#ifdef WITH_CYCLES
static bool get_show_adaptive_options(const bContext *C, Panel *panel)
{
/* Don't show adaptive options if cycles isn't the active engine. */
@@ -331,15 +331,8 @@ static bool get_show_adaptive_options(const bContext *C, Panel *panel)
return false;
}
/* Don't show adaptive options if the cycles experimental feature set is disabled. */
Scene *scene = CTX_data_scene(C);
if (!BKE_scene_uses_cycles_experimental_features(scene)) {
return false;
}
return true;
}
#endif
static void panel_draw(const bContext *C, Panel *panel)
{
@@ -348,27 +341,6 @@ static void panel_draw(const bContext *C, Panel *panel)
PointerRNA ob_ptr;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
/* Only test for adaptive subdivision if built with cycles. */
bool show_adaptive_options = false;
bool ob_use_adaptive_subdivision = false;
PointerRNA cycles_ptr = {};
PointerRNA ob_cycles_ptr = {};
#ifdef WITH_CYCLES
Scene *scene = CTX_data_scene(C);
PointerRNA scene_ptr = RNA_id_pointer_create(&scene->id);
if (BKE_scene_uses_cycles(scene)) {
cycles_ptr = RNA_pointer_get(&scene_ptr, "cycles");
ob_cycles_ptr = RNA_pointer_get(&ob_ptr, "cycles");
if (!RNA_pointer_is_null(&ob_cycles_ptr)) {
show_adaptive_options = get_show_adaptive_options(C, panel);
ob_use_adaptive_subdivision = show_adaptive_options &&
RNA_boolean_get(&ob_cycles_ptr, "use_adaptive_subdivision");
}
}
#else
UNUSED_VARS(C);
#endif
layout->prop(ptr, "subdivision_type", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
layout->use_property_split_set(true);
@@ -402,31 +374,47 @@ static void panel_draw(const bContext *C, Panel *panel)
}
}
if (show_adaptive_options) {
if (get_show_adaptive_options(C, panel)) {
PanelLayout adaptive_panel = layout->panel_prop_with_bool_header(
C,
ptr,
"open_adaptive_subdivision_panel",
&ob_cycles_ptr,
ptr,
"use_adaptive_subdivision",
IFACE_("Adaptive Subdivision"));
if (adaptive_panel.body) {
adaptive_panel.body->active_set(ob_use_adaptive_subdivision);
adaptive_panel.body->prop(
&ob_cycles_ptr, "dicing_rate", UI_ITEM_NONE, std::nullopt, ICON_NONE);
Scene *scene = CTX_data_scene(C);
PointerRNA scene_ptr = RNA_id_pointer_create(&scene->id);
PointerRNA cycles_ptr = RNA_pointer_get(&scene_ptr, "cycles");
const float render_rate = RNA_float_get(&cycles_ptr, "dicing_rate");
const float preview_rate = RNA_float_get(&cycles_ptr, "preview_dicing_rate");
std::string render_str, preview_str;
float render = std::max(RNA_float_get(&cycles_ptr, "dicing_rate") *
RNA_float_get(&ob_cycles_ptr, "dicing_rate"),
0.1f);
float preview = std::max(RNA_float_get(&cycles_ptr, "preview_dicing_rate") *
RNA_float_get(&ob_cycles_ptr, "dicing_rate"),
0.1f);
adaptive_panel.body->active_set(smd->flags & eSubsurfModifierFlag_UseAdaptiveSubdivision);
adaptive_panel.body->prop(ptr, "adaptive_space", UI_ITEM_NONE, IFACE_("Space"), ICON_NONE);
if (smd->adaptive_space == SUBSURF_ADAPTIVE_SPACE_OBJECT) {
adaptive_panel.body->prop(
ptr, "adaptive_object_edge_length", UI_ITEM_NONE, std::nullopt, ICON_NONE);
preview_str = fmt::format("{:.5g}", preview_rate * smd->adaptive_object_edge_length);
render_str = fmt::format("{:.5g}", render_rate * smd->adaptive_object_edge_length);
}
else {
adaptive_panel.body->prop(
ptr, "adaptive_pixel_size", UI_ITEM_NONE, std::nullopt, ICON_NONE);
preview_str = fmt::format("{:.2f} px",
std::max(preview_rate * smd->adaptive_pixel_size, 0.1f));
render_str = fmt::format("{:.2f} px",
std::max(render_rate * smd->adaptive_pixel_size, 0.1f));
}
uiLayout *split = &adaptive_panel.body->split(0.4f, false);
split->column(true).label("", ICON_NONE);
uiLayout *col = &split->column(true);
col->label(fmt::format(fmt::runtime(RPT_("Viewport {:.2f} px")), preview), ICON_NONE);
col->label(fmt::format(fmt::runtime(RPT_("Render {:.2f} px")), render), ICON_NONE);
col->alignment_set(blender::ui::LayoutAlign::Right);
col->label(IFACE_("Viewport"), ICON_NONE);
col->label(IFACE_("Render"), ICON_NONE);
col = &split->column(true);
col->label(preview_str, ICON_NONE);
col->label(render_str, ICON_NONE);
}
}
@@ -438,7 +426,8 @@ static void panel_draw(const bContext *C, Panel *panel)
advanced_layout->prop(ptr, "use_limit_surface", UI_ITEM_NONE, std::nullopt, ICON_NONE);
uiLayout *col = &advanced_layout->column(true);
col->active_set(ob_use_adaptive_subdivision || RNA_boolean_get(ptr, "use_limit_surface"));
col->active_set((smd->flags & eSubsurfModifierFlag_UseAdaptiveSubdivision) ||
RNA_boolean_get(ptr, "use_limit_surface"));
col->prop(ptr, "quality", UI_ITEM_NONE, std::nullopt, ICON_NONE);
advanced_layout->prop(ptr, "uv_smooth", UI_ITEM_NONE, std::nullopt, ICON_NONE);

Binary file not shown.

Binary file not shown.

BIN
tests/files/render/displacement/object_dicing.blend (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.