Cycles: experimental integration of Alembic procedural in viewport rendering
This patch exposes the Cycles Alembic Procedural through the MeshSequenceCache modifier in order to use and test it from Blender. To enable it, one has to switch the render feature set to experimental and activate the Procedural in the modifier. An Alembic Procedural is then created for each CacheFile from Blender set to use the Procedural, and each Blender object having a MeshSequenceCache modifier is added to list of objects of the right procedural. The procedural's parameters derive from the CacheFile's properties which are already exposed in the UI through the modifier, although more Cycles specific options might be added in the future. As there is currently no cache controls and since we load all the data at the beginning of the render session, the procedural is only available during viewport renders at the moment. When an Alembic procedural is rendered, data from the archive are not read on the Blender side. If a Cycles render is not active and the CacheFile is set to use the Cycles Procedural, bounding boxes are used to display the objects in the scene as a signal that the objects are not processed by Blender anymore. This is standard in other DCCs. However this does not reduce the memory usage from Blender as the Alembic data was already loaded either during an import or during a .blend file read. This is mostly a hack to test the Cycles Alembic procedural until we have a better Blender side mechanism for letting renderers load their own geometry, which will be based on import and export settings on Collections (T68933). Ref T79174, D3089 Reviewed By: brecht, sybren Maniphest Tasks: T79174 Differential Revision: https://developer.blender.org/D10197
This commit is contained in:
parent
5b97c00e9f
commit
51862c8445
Notes:
blender-bot
2023-02-14 07:17:43 +01:00
Referenced by issue #79174, Cycles Procedural API and faster scene update
|
@ -115,6 +115,16 @@ if(WITH_OPENVDB)
|
|||
)
|
||||
endif()
|
||||
|
||||
if(WITH_ALEMBIC)
|
||||
add_definitions(-DWITH_ALEMBIC)
|
||||
list(APPEND INC_SYS
|
||||
${ALEMBIC_INCLUDE_DIRS}
|
||||
)
|
||||
list(APPEND LIB
|
||||
${ALEMBIC_LIBRARIES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WITH_OPENIMAGEDENOISE)
|
||||
add_definitions(-DWITH_OPENIMAGEDENOISE)
|
||||
list(APPEND INC_SYS
|
||||
|
|
|
@ -61,6 +61,7 @@ class CyclesRender(bpy.types.RenderEngine):
|
|||
bl_use_save_buffers = True
|
||||
bl_use_spherical_stereo = True
|
||||
bl_use_custom_freestyle = True
|
||||
bl_use_alembic_procedural = True
|
||||
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
|
|
|
@ -227,6 +227,11 @@ def update_render_passes(self, context):
|
|||
view_layer.update_render_passes()
|
||||
|
||||
|
||||
def update_render_engine(self, context):
|
||||
scene = context.scene
|
||||
scene.update_render_engine()
|
||||
|
||||
|
||||
class CyclesRenderSettings(bpy.types.PropertyGroup):
|
||||
|
||||
device: EnumProperty(
|
||||
|
@ -240,6 +245,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
|
|||
description="Feature set to use for rendering",
|
||||
items=enum_feature_set,
|
||||
default='SUPPORTED',
|
||||
update=update_render_engine,
|
||||
)
|
||||
shading_system: BoolProperty(
|
||||
name="Open Shading Language",
|
||||
|
|
|
@ -1038,23 +1038,6 @@ static void create_subd_mesh(Scene *scene,
|
|||
|
||||
/* Sync */
|
||||
|
||||
static BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob)
|
||||
{
|
||||
if (b_ob.modifiers.length() > 0) {
|
||||
BL::Modifier b_mod = b_ob.modifiers[b_ob.modifiers.length() - 1];
|
||||
|
||||
if (b_mod.type() == BL::Modifier::type_MESH_SEQUENCE_CACHE) {
|
||||
BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(b_mod);
|
||||
|
||||
if (MeshSequenceCacheModifier_has_velocity_get(&mesh_cache.ptr)) {
|
||||
return mesh_cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BL::MeshSequenceCacheModifier(PointerRNA_NULL);
|
||||
}
|
||||
|
||||
/* Check whether some of "built-in" motion-related attributes are needed to be exported (includes
|
||||
* things like velocity from cache modifier, fluid simulation).
|
||||
*
|
||||
|
@ -1095,7 +1078,7 @@ static void sync_mesh_cached_velocities(BL::Object &b_ob, Scene *scene, Mesh *me
|
|||
return;
|
||||
}
|
||||
|
||||
BL::MeshSequenceCacheModifier b_mesh_cache = object_mesh_cache_find(b_ob);
|
||||
BL::MeshSequenceCacheModifier b_mesh_cache = object_mesh_cache_find(b_ob, true);
|
||||
|
||||
if (!b_mesh_cache) {
|
||||
return;
|
||||
|
@ -1258,7 +1241,7 @@ void BlenderSync::sync_mesh_motion(BL::Depsgraph b_depsgraph,
|
|||
}
|
||||
|
||||
/* Cached motion blur already exported. */
|
||||
BL::MeshSequenceCacheModifier mesh_cache = object_mesh_cache_find(b_ob);
|
||||
BL::MeshSequenceCacheModifier mesh_cache = object_mesh_cache_find(b_ob, true);
|
||||
if (mesh_cache) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "render/alembic.h"
|
||||
#include "render/camera.h"
|
||||
#include "render/graph.h"
|
||||
#include "render/integrator.h"
|
||||
|
@ -484,6 +485,64 @@ bool BlenderSync::sync_object_attributes(BL::DepsgraphObjectInstance &b_instance
|
|||
|
||||
/* Object Loop */
|
||||
|
||||
void BlenderSync::sync_procedural(BL::Object &b_ob, BL::MeshSequenceCacheModifier &b_mesh_cache)
|
||||
{
|
||||
#ifdef WITH_ALEMBIC
|
||||
BL::CacheFile cache_file = b_mesh_cache.cache_file();
|
||||
void *cache_file_key = cache_file.ptr.data;
|
||||
|
||||
AlembicProcedural *procedural = static_cast<AlembicProcedural *>(
|
||||
procedural_map.find(cache_file_key));
|
||||
|
||||
if (procedural == nullptr) {
|
||||
procedural = scene->create_node<AlembicProcedural>();
|
||||
procedural_map.add(cache_file_key, procedural);
|
||||
}
|
||||
else {
|
||||
procedural_map.used(procedural);
|
||||
}
|
||||
|
||||
float current_frame = b_scene.frame_current();
|
||||
if (cache_file.override_frame()) {
|
||||
current_frame = cache_file.frame();
|
||||
}
|
||||
|
||||
if (!cache_file.override_frame()) {
|
||||
procedural->set_start_frame(b_scene.frame_start());
|
||||
procedural->set_end_frame(b_scene.frame_end());
|
||||
}
|
||||
|
||||
procedural->set_frame(current_frame);
|
||||
procedural->set_frame_rate(b_scene.render().fps() / b_scene.render().fps_base());
|
||||
procedural->set_frame_offset(cache_file.frame_offset());
|
||||
|
||||
string absolute_path = blender_absolute_path(b_data, b_ob, b_mesh_cache.cache_file().filepath());
|
||||
procedural->set_filepath(ustring(absolute_path));
|
||||
|
||||
procedural->set_scale(cache_file.scale());
|
||||
|
||||
/* create or update existing AlembicObjects */
|
||||
ustring object_path = ustring(b_mesh_cache.object_path());
|
||||
|
||||
AlembicObject *abc_object = procedural->get_or_create_object(object_path);
|
||||
|
||||
array<Node *> used_shaders = find_used_shaders(b_ob);
|
||||
abc_object->set_used_shaders(used_shaders);
|
||||
|
||||
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);
|
||||
abc_object->set_subd_dicing_rate(subd_dicing_rate);
|
||||
abc_object->set_subd_max_level(max_subdivisions);
|
||||
|
||||
if (abc_object->is_modified() || procedural->is_modified()) {
|
||||
procedural->tag_update(scene);
|
||||
}
|
||||
#else
|
||||
(void)b_ob;
|
||||
(void)b_mesh_cache;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph,
|
||||
BL::SpaceView3D &b_v3d,
|
||||
float motion_time)
|
||||
|
@ -499,6 +558,7 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph,
|
|||
light_map.pre_sync();
|
||||
geometry_map.pre_sync();
|
||||
object_map.pre_sync();
|
||||
procedural_map.pre_sync();
|
||||
particle_system_map.pre_sync();
|
||||
motion_times.clear();
|
||||
}
|
||||
|
@ -539,15 +599,38 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph,
|
|||
|
||||
/* Object itself. */
|
||||
if (b_instance.show_self()) {
|
||||
sync_object(b_depsgraph,
|
||||
b_view_layer,
|
||||
b_instance,
|
||||
motion_time,
|
||||
false,
|
||||
show_lights,
|
||||
culling,
|
||||
&use_portal,
|
||||
sync_hair ? NULL : &geom_task_pool);
|
||||
#ifdef WITH_ALEMBIC
|
||||
bool use_procedural = false;
|
||||
BL::MeshSequenceCacheModifier b_mesh_cache(PointerRNA_NULL);
|
||||
|
||||
/* Experimental as Blender does not have good support for procedurals at the moment, also
|
||||
* only available in preview renders since currently do not have a good cache policy, the
|
||||
* data being loaded at once for all the frames. */
|
||||
if (experimental && b_v3d) {
|
||||
b_mesh_cache = object_mesh_cache_find(b_ob, false);
|
||||
use_procedural = b_mesh_cache && b_mesh_cache.cache_file().use_render_procedural();
|
||||
}
|
||||
|
||||
if (use_procedural) {
|
||||
/* Skip in the motion case, as generating motion blur data will be handled in the
|
||||
* procedural. */
|
||||
if (!motion) {
|
||||
sync_procedural(b_ob, b_mesh_cache);
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
sync_object(b_depsgraph,
|
||||
b_view_layer,
|
||||
b_instance,
|
||||
motion_time,
|
||||
false,
|
||||
show_lights,
|
||||
culling,
|
||||
&use_portal,
|
||||
sync_hair ? NULL : &geom_task_pool);
|
||||
}
|
||||
}
|
||||
|
||||
/* Particle hair as separate object. */
|
||||
|
@ -580,6 +663,7 @@ void BlenderSync::sync_objects(BL::Depsgraph &b_depsgraph,
|
|||
object_map.post_sync();
|
||||
geometry_map.post_sync();
|
||||
particle_system_map.post_sync();
|
||||
procedural_map.post_sync();
|
||||
}
|
||||
|
||||
if (motion)
|
||||
|
|
|
@ -59,6 +59,7 @@ BlenderSync::BlenderSync(BL::RenderEngine &b_engine,
|
|||
b_scene(b_scene),
|
||||
shader_map(scene),
|
||||
object_map(scene),
|
||||
procedural_map(scene),
|
||||
geometry_map(scene),
|
||||
light_map(scene),
|
||||
particle_system_map(scene),
|
||||
|
|
|
@ -151,6 +151,8 @@ class BlenderSync {
|
|||
TaskPool *geom_task_pool);
|
||||
void sync_object_motion_init(BL::Object &b_parent, BL::Object &b_ob, Object *object);
|
||||
|
||||
void sync_procedural(BL::Object &b_ob, BL::MeshSequenceCacheModifier &b_mesh_cache);
|
||||
|
||||
bool sync_object_attributes(BL::DepsgraphObjectInstance &b_instance, Object *object);
|
||||
|
||||
/* Volume */
|
||||
|
@ -221,6 +223,7 @@ class BlenderSync {
|
|||
|
||||
id_map<void *, Shader> shader_map;
|
||||
id_map<ObjectKey, Object> object_map;
|
||||
id_map<void *, Procedural> procedural_map;
|
||||
id_map<GeometryKey, Geometry> geometry_map;
|
||||
id_map<ObjectKey, Light> light_map;
|
||||
id_map<ParticleSystemKey, ParticleSystem> particle_system_map;
|
||||
|
|
|
@ -572,6 +572,35 @@ static inline BL::FluidDomainSettings object_fluid_gas_domain_find(BL::Object &b
|
|||
return BL::FluidDomainSettings(PointerRNA_NULL);
|
||||
}
|
||||
|
||||
static inline BL::MeshSequenceCacheModifier object_mesh_cache_find(BL::Object &b_ob,
|
||||
bool check_velocity)
|
||||
{
|
||||
for (int i = b_ob.modifiers.length() - 1; i >= 0; --i) {
|
||||
BL::Modifier b_mod = b_ob.modifiers[i];
|
||||
|
||||
if (b_mod.type() == BL::Modifier::type_MESH_SEQUENCE_CACHE) {
|
||||
BL::MeshSequenceCacheModifier mesh_cache = BL::MeshSequenceCacheModifier(b_mod);
|
||||
|
||||
if (check_velocity) {
|
||||
if (!MeshSequenceCacheModifier_has_velocity_get(&mesh_cache.ptr)) {
|
||||
return BL::MeshSequenceCacheModifier(PointerRNA_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return mesh_cache;
|
||||
}
|
||||
|
||||
/* Skip possible particles system modifiers as they do not modify the geometry. */
|
||||
if (b_mod.type() == BL::Modifier::type_PARTICLE_SYSTEM) {
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return BL::MeshSequenceCacheModifier(PointerRNA_NULL);
|
||||
}
|
||||
|
||||
static inline Mesh::SubdivisionType object_subdivision_type(BL::Object &b_ob,
|
||||
bool preview,
|
||||
bool experimental)
|
||||
|
|
|
@ -32,6 +32,7 @@ struct CacheReader;
|
|||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Object;
|
||||
struct Scene;
|
||||
|
||||
void BKE_cachefiles_init(void);
|
||||
void BKE_cachefiles_exit(void);
|
||||
|
@ -60,6 +61,15 @@ void BKE_cachefile_reader_open(struct CacheFile *cache_file,
|
|||
const char *object_path);
|
||||
void BKE_cachefile_reader_free(struct CacheFile *cache_file, struct CacheReader **reader);
|
||||
|
||||
/* Determine whether the CacheFile should use a render engine procedural. If so, data is not read
|
||||
* from the file and bouding boxes are used to represent the objects in the Scene. Render engines
|
||||
* will receive the bounding box as a placeholder but can instead load the data directly if they
|
||||
* support it.
|
||||
*/
|
||||
bool BKE_cache_file_uses_render_procedural(const struct CacheFile *cache_file,
|
||||
struct Scene *scene,
|
||||
const int dag_eval_mode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -319,8 +319,10 @@ typedef struct ModifierTypeInfo {
|
|||
* changes.
|
||||
*
|
||||
* This function is optional (assumes false if not present).
|
||||
*
|
||||
* The dag_eval_mode should be of type eEvaluationMode.
|
||||
*/
|
||||
bool (*dependsOnTime)(struct ModifierData *md);
|
||||
bool (*dependsOnTime)(struct Scene *scene, struct ModifierData *md, const int dag_eval_mode);
|
||||
|
||||
/**
|
||||
* True when a deform modifier uses normals, the requiredDataMask
|
||||
|
@ -425,7 +427,9 @@ void BKE_modifier_copydata(struct ModifierData *md, struct ModifierData *target)
|
|||
void BKE_modifier_copydata_ex(struct ModifierData *md,
|
||||
struct ModifierData *target,
|
||||
const int flag);
|
||||
bool BKE_modifier_depends_ontime(struct ModifierData *md);
|
||||
bool BKE_modifier_depends_ontime(struct Scene *scene,
|
||||
struct ModifierData *md,
|
||||
int dag_eval_mode);
|
||||
bool BKE_modifier_supports_mapping(struct ModifierData *md);
|
||||
bool BKE_modifier_supports_cage(struct Scene *scene, struct ModifierData *md);
|
||||
bool BKE_modifier_couldbe_cage(struct Scene *scene, struct ModifierData *md);
|
||||
|
|
|
@ -401,7 +401,10 @@ void BKE_object_groups_clear(struct Main *bmain, struct Scene *scene, struct Obj
|
|||
|
||||
struct KDTree_3d *BKE_object_as_kdtree(struct Object *ob, int *r_tot);
|
||||
|
||||
bool BKE_object_modifier_use_time(struct Object *ob, struct ModifierData *md);
|
||||
bool BKE_object_modifier_use_time(struct Scene *scene,
|
||||
struct Object *ob,
|
||||
struct ModifierData *md,
|
||||
int dag_eval_mode);
|
||||
|
||||
bool BKE_object_modifier_update_subframe(struct Depsgraph *depsgraph,
|
||||
struct Scene *scene,
|
||||
|
|
|
@ -174,6 +174,10 @@ bool BKE_scene_uses_blender_eevee(const struct Scene *scene);
|
|||
bool BKE_scene_uses_blender_workbench(const struct Scene *scene);
|
||||
bool BKE_scene_uses_cycles(const struct 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). */
|
||||
bool BKE_scene_uses_cycles_experimental_features(struct Scene *scene);
|
||||
|
||||
void BKE_scene_copy_data_eevee(struct Scene *sce_dst, const struct Scene *sce_src);
|
||||
|
||||
void BKE_scene_disable_color_management(struct Scene *scene);
|
||||
|
|
|
@ -49,6 +49,8 @@
|
|||
|
||||
#include "DEG_depsgraph_query.h"
|
||||
|
||||
#include "RE_engine.h"
|
||||
|
||||
#include "BLO_read_write.h"
|
||||
|
||||
#ifdef WITH_ALEMBIC
|
||||
|
@ -409,3 +411,19 @@ float BKE_cachefile_time_offset(const CacheFile *cache_file, const float time, c
|
|||
const float frame = (cache_file->override_frame ? cache_file->frame : time);
|
||||
return cache_file->is_sequence ? frame : frame / fps - time_offset;
|
||||
}
|
||||
|
||||
bool BKE_cache_file_uses_render_procedural(const CacheFile *cache_file,
|
||||
Scene *scene,
|
||||
const int dag_eval_mode)
|
||||
{
|
||||
RenderEngineType *render_engine_type = RE_engines_find(scene->r.engine);
|
||||
|
||||
if (cache_file->type != CACHEFILE_TYPE_ALEMBIC ||
|
||||
!RE_engine_supports_alembic_procedural(render_engine_type, scene)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The render time procedural is only enabled during viewport rendering. */
|
||||
const bool is_final_render = (eEvaluationMode)dag_eval_mode == DAG_EVAL_RENDER;
|
||||
return cache_file->use_render_procedural && !is_final_render;
|
||||
}
|
||||
|
|
|
@ -5430,6 +5430,11 @@ static void transformcache_evaluate(bConstraint *con, bConstraintOb *cob, ListBa
|
|||
return;
|
||||
}
|
||||
|
||||
/* Do not process data if using a render time procedural. */
|
||||
if (BKE_cache_file_uses_render_procedural(cache_file, scene, DEG_get_mode(cob->depsgraph))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float frame = DEG_get_ctime(cob->depsgraph);
|
||||
const float time = BKE_cachefile_time_offset(cache_file, frame, FPS);
|
||||
|
||||
|
|
|
@ -249,11 +249,11 @@ bool BKE_modifier_unique_name(ListBase *modifiers, ModifierData *md)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool BKE_modifier_depends_ontime(ModifierData *md)
|
||||
bool BKE_modifier_depends_ontime(Scene *scene, ModifierData *md, const int dag_eval_mode)
|
||||
{
|
||||
const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
|
||||
|
||||
return mti->dependsOnTime && mti->dependsOnTime(md);
|
||||
return mti->dependsOnTime && mti->dependsOnTime(scene, md, dag_eval_mode);
|
||||
}
|
||||
|
||||
bool BKE_modifier_supports_mapping(ModifierData *md)
|
||||
|
|
|
@ -5411,9 +5411,12 @@ KDTree_3d *BKE_object_as_kdtree(Object *ob, int *r_tot)
|
|||
return tree;
|
||||
}
|
||||
|
||||
bool BKE_object_modifier_use_time(Object *ob, ModifierData *md)
|
||||
bool BKE_object_modifier_use_time(Scene *scene,
|
||||
Object *ob,
|
||||
ModifierData *md,
|
||||
const int dag_eval_mode)
|
||||
{
|
||||
if (BKE_modifier_depends_ontime(md)) {
|
||||
if (BKE_modifier_depends_ontime(scene, md, dag_eval_mode)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,6 +109,8 @@
|
|||
|
||||
#include "RE_engine.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
|
||||
#include "SEQ_edit.h"
|
||||
#include "SEQ_iterator.h"
|
||||
#include "SEQ_modifier.h"
|
||||
|
@ -2938,6 +2940,22 @@ bool BKE_scene_uses_cycles(const Scene *scene)
|
|||
return STREQ(scene->r.engine, RE_engine_id_CYCLES);
|
||||
}
|
||||
|
||||
/* This enumeration has to match the one defined in the Cycles addon. */
|
||||
typedef enum eCyclesFeatureSet {
|
||||
CYCLES_FEATURES_SUPPORTED = 0,
|
||||
CYCLES_FEATURES_EXPERIMENTAL = 1,
|
||||
} eCyclesFeatureSet;
|
||||
|
||||
/* We cannot use const as RNA_id_pointer_create is not using a const ID. */
|
||||
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, &scene_ptr);
|
||||
PointerRNA cycles_ptr = RNA_pointer_get(&scene_ptr, "cycles");
|
||||
return RNA_enum_get(&cycles_ptr, "feature_set") == CYCLES_FEATURES_EXPERIMENTAL;
|
||||
}
|
||||
|
||||
void BKE_scene_base_flag_to_objects(ViewLayer *view_layer)
|
||||
{
|
||||
Base *base = view_layer->object_bases.first;
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
#include "intern/node/deg_node_operation.h"
|
||||
#include "intern/node/deg_node_time.h"
|
||||
|
||||
#include "intern/depsgraph.h"
|
||||
#include "intern/depsgraph_relation.h"
|
||||
#include "intern/depsgraph_type.h"
|
||||
|
||||
|
@ -2095,7 +2096,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object)
|
|||
ctx.node = reinterpret_cast<::DepsNodeHandle *>(&handle);
|
||||
mti->updateDepsgraph(md, &ctx);
|
||||
}
|
||||
if (BKE_object_modifier_use_time(object, md)) {
|
||||
if (BKE_object_modifier_use_time(scene_, object, md, graph_->mode)) {
|
||||
TimeSourceKey time_src_key;
|
||||
add_relation(time_src_key, obdata_ubereval_key, "Time Source");
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@
|
|||
#include "ED_screen.h"
|
||||
#include "ED_undo.h"
|
||||
|
||||
#include "RE_engine.h"
|
||||
|
||||
#include "RNA_access.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
@ -6463,6 +6465,29 @@ void uiTemplateCacheFile(uiLayout *layout,
|
|||
row = uiLayoutRow(layout, false);
|
||||
uiItemR(row, &fileptr, "is_sequence", 0, NULL, ICON_NONE);
|
||||
|
||||
/* Only enable render procedural option if the active engine supports it. */
|
||||
const struct RenderEngineType *engine_type = CTX_data_engine_type(C);
|
||||
|
||||
Scene *scene = CTX_data_scene(C);
|
||||
const bool engine_supports_procedural = RE_engine_supports_alembic_procedural(engine_type, scene);
|
||||
|
||||
if (!engine_supports_procedural) {
|
||||
row = uiLayoutRow(layout, false);
|
||||
/* For Cycles, verify that experimental features are enabled. */
|
||||
if (BKE_scene_uses_cycles(scene) && !BKE_scene_uses_cycles_experimental_features(scene)) {
|
||||
uiItemL(row,
|
||||
"The Cycles Alembic Procedural is only available with the experimental feature set",
|
||||
ICON_INFO);
|
||||
}
|
||||
else {
|
||||
uiItemL(row, "The active render engine does not have an Alembic Procedural", ICON_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
row = uiLayoutRow(layout, false);
|
||||
uiLayoutSetActive(row, engine_supports_procedural);
|
||||
uiItemR(row, &fileptr, "use_render_procedural", 0, NULL, ICON_NONE);
|
||||
|
||||
row = uiLayoutRowWithHeading(layout, true, IFACE_("Override Frame"));
|
||||
sub = uiLayoutRow(row, true);
|
||||
uiLayoutSetPropDecorate(sub, false);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "DNA_cachefile_types.h"
|
||||
#include "DNA_light_types.h"
|
||||
#include "DNA_material_types.h"
|
||||
#include "DNA_node_types.h"
|
||||
|
@ -204,6 +205,19 @@ void ED_render_engine_changed(Main *bmain, const bool update_scene_data)
|
|||
ntreeCompositUpdateRLayers(scene->nodetree);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update CacheFiles to ensure that procedurals are properly taken into account. */
|
||||
LISTBASE_FOREACH (CacheFile *, cachefile, &bmain->cachefiles) {
|
||||
/* Only update cachefiles which are set to use a render procedural. We do not use
|
||||
* BKE_cachefile_uses_render_procedural here as we need to update regardless of the current
|
||||
* engine or its settings. */
|
||||
if (cachefile->use_render_procedural) {
|
||||
DEG_id_tag_update(&cachefile->id, ID_RECALC_COPY_ON_WRITE);
|
||||
/* Rebuild relations so that modifiers are reconnected to or disconnected from the cachefile.
|
||||
*/
|
||||
DEG_relations_tag_update(bmain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ED_render_view_layer_changed(Main *bmain, bScreen *screen)
|
||||
|
|
|
@ -87,14 +87,21 @@ typedef struct CacheFile {
|
|||
/** The frame offset to subtract. */
|
||||
float frame_offset;
|
||||
|
||||
char _pad[4];
|
||||
|
||||
/** Animation flag. */
|
||||
short flag;
|
||||
short draw_flag; /* UNUSED */
|
||||
|
||||
/* eCacheFileType enum. */
|
||||
char type;
|
||||
|
||||
char _pad[2];
|
||||
/** Do not load data from the cache file and display objects in the scene as boxes, Cycles will
|
||||
* load objects directly from the CacheFile. Other render engines which can load Alembic data
|
||||
* directly can take care of rendering it themselves.
|
||||
*/
|
||||
char use_render_procedural;
|
||||
|
||||
char _pad1[7];
|
||||
|
||||
char velocity_unit;
|
||||
/* Name of the velocity property in the archive. */
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
# include "BKE_cachefile.h"
|
||||
|
||||
# include "DEG_depsgraph.h"
|
||||
# include "DEG_depsgraph_build.h"
|
||||
|
||||
# include "WM_api.h"
|
||||
# include "WM_types.h"
|
||||
|
@ -53,6 +54,12 @@ static void rna_CacheFile_update(Main *UNUSED(bmain), Scene *UNUSED(scene), Poin
|
|||
WM_main_add_notifier(NC_OBJECT | ND_DRAW, NULL);
|
||||
}
|
||||
|
||||
static void rna_CacheFile_dependency_update(Main *bmain, Scene *scene, PointerRNA *ptr)
|
||||
{
|
||||
rna_CacheFile_update(bmain, scene, ptr);
|
||||
DEG_relations_tag_update(bmain);
|
||||
}
|
||||
|
||||
static void rna_CacheFile_object_paths_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
||||
{
|
||||
CacheFile *cache_file = (CacheFile *)ptr->data;
|
||||
|
@ -105,6 +112,16 @@ static void rna_def_cachefile(BlenderRNA *brna)
|
|||
prop, "Sequence", "Whether the cache is separated in a series of files");
|
||||
RNA_def_property_update(prop, 0, "rna_CacheFile_update");
|
||||
|
||||
prop = RNA_def_property(srna, "use_render_procedural", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Use Render Engine Procedural",
|
||||
"Display boxes in the viewport as placeholders for the objects, Cycles will use a "
|
||||
"procedural to load the objects during viewport rendering in experimental mode, "
|
||||
"other render engines will also receive a placeholder and should take care of loading the "
|
||||
"Alembic data themselves if possible");
|
||||
RNA_def_property_update(prop, 0, "rna_CacheFile_dependency_update");
|
||||
|
||||
/* ----------------- For Scene time ------------------- */
|
||||
|
||||
prop = RNA_def_property(srna, "override_frame", PROP_BOOLEAN, PROP_NONE);
|
||||
|
|
|
@ -896,6 +896,12 @@ static void rna_def_render_engine(BlenderRNA *brna)
|
|||
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
|
||||
RNA_def_property_ui_text(prop, "Use Stereo Viewport", "Support rendering stereo 3D viewport");
|
||||
|
||||
prop = RNA_def_property(srna, "bl_use_alembic_procedural", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, NULL, "type->flag", RE_USE_ALEMBIC_PROCEDURAL);
|
||||
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Use Alembic Procedural", "Support loading Alembic data at render time");
|
||||
|
||||
RNA_define_verify_sdna(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -616,6 +616,7 @@ const EnumPropertyItem rna_enum_transform_orientation_items[] = {
|
|||
# include "BLI_string_utils.h"
|
||||
|
||||
# include "DNA_anim_types.h"
|
||||
# include "DNA_cachefile_types.h"
|
||||
# include "DNA_color_types.h"
|
||||
# include "DNA_mesh_types.h"
|
||||
# include "DNA_node_types.h"
|
||||
|
@ -1619,6 +1620,11 @@ static void rna_RenderSettings_engine_update(Main *bmain,
|
|||
ED_render_engine_changed(bmain, true);
|
||||
}
|
||||
|
||||
static void rna_Scene_update_render_engine(Main *bmain)
|
||||
{
|
||||
ED_render_engine_changed(bmain, true);
|
||||
}
|
||||
|
||||
static bool rna_RenderSettings_multiple_engines_get(PointerRNA *UNUSED(ptr))
|
||||
{
|
||||
return (BLI_listbase_count(&R_engines) > 1);
|
||||
|
@ -7836,6 +7842,10 @@ void RNA_def_scene(BlenderRNA *brna)
|
|||
RNA_def_property_update(prop, NC_SCENE, NULL);
|
||||
RNA_def_property_update(prop, NC_SCENE, "rna_Scene_volume_update");
|
||||
|
||||
func = RNA_def_function(srna, "update_render_engine", "rna_Scene_update_render_engine");
|
||||
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_MAIN);
|
||||
RNA_def_function_ui_description(func, "Trigger a render engine update");
|
||||
|
||||
/* Statistics */
|
||||
func = RNA_def_function(srna, "statistics", "rna_Scene_statistics_string_get");
|
||||
RNA_def_function_flag(func, FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
|
|
|
@ -61,7 +61,9 @@ static void initData(ModifierData *md)
|
|||
MEMCPY_STRUCT_AFTER(bmd, DNA_struct_default_get(BuildModifierData), modifier);
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -220,7 +220,9 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla
|
|||
tclmd->solver_result = NULL;
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,9 @@ static void freeData(ModifierData *md)
|
|||
}
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,9 @@ static void requiredDataMask(Object *UNUSED(ob),
|
|||
}
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *md,
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
DisplaceModifierData *dmd = (DisplaceModifierData *)md;
|
||||
|
||||
|
|
|
@ -155,7 +155,9 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte
|
|||
}
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -86,7 +86,9 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla
|
|||
|
||||
temd->facepa = NULL;
|
||||
}
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -132,7 +132,9 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
|
|||
#endif /* WITH_FLUID */
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -63,7 +63,9 @@ static void initData(ModifierData *md)
|
|||
MEMCPY_STRUCT_AFTER(mcmd, DNA_struct_default_get(MeshCacheModifierData), modifier);
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *md,
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
|
||||
return (mcmd->play_mode == MOD_MESHCACHE_PLAY_CFEA);
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
|
@ -39,6 +40,8 @@
|
|||
#include "BKE_cachefile.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_lib_query.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_object.h"
|
||||
#include "BKE_scene.h"
|
||||
#include "BKE_screen.h"
|
||||
|
||||
|
@ -118,6 +121,44 @@ static bool isDisabled(const struct Scene *UNUSED(scene),
|
|||
return (mcmd->cache_file == NULL) || (mcmd->object_path[0] == '\0');
|
||||
}
|
||||
|
||||
static Mesh *generate_bounding_box_mesh(Object *object, Mesh *org_mesh)
|
||||
{
|
||||
BoundBox *bb = BKE_object_boundbox_get(object);
|
||||
Mesh *result = BKE_mesh_new_nomain_from_template(org_mesh, 8, 0, 0, 24, 6);
|
||||
|
||||
MVert *mvert = result->mvert;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
copy_v3_v3(mvert[i].co, bb->vec[i]);
|
||||
}
|
||||
|
||||
/* See DNA_object_types.h for the diagram showing the order of the vertices for a BoundBox. */
|
||||
static unsigned int loops_v[6][4] = {
|
||||
{0, 4, 5, 1},
|
||||
{4, 7, 6, 5},
|
||||
{7, 3, 2, 6},
|
||||
{3, 0, 1, 2},
|
||||
{1, 5, 6, 2},
|
||||
{3, 7, 4, 0},
|
||||
};
|
||||
|
||||
MLoop *mloop = result->mloop;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
for (int j = 0; j < 4; ++j, ++mloop) {
|
||||
mloop->v = loops_v[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
MPoly *mpoly = result->mpoly;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
mpoly[i].loopstart = i * 4;
|
||||
mpoly[i].totloop = 4;
|
||||
}
|
||||
|
||||
BKE_mesh_calc_edges(result, false, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
|
||||
{
|
||||
#if defined(WITH_USD) || defined(WITH_ALEMBIC)
|
||||
|
@ -143,6 +184,12 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
|
|||
}
|
||||
}
|
||||
|
||||
/* Do not process data if using a render procedural, return a box instead for displaying in the
|
||||
* viewport. */
|
||||
if (BKE_cache_file_uses_render_procedural(cache_file, scene, DEG_get_mode(ctx->depsgraph))) {
|
||||
return generate_bounding_box_mesh(ctx->object, org_mesh);
|
||||
}
|
||||
|
||||
/* If this invocation is for the ORCO mesh, and the mesh hasn't changed topology, we
|
||||
* must return the mesh as-is instead of deforming it. */
|
||||
if (ctx->flag & MOD_APPLY_ORCO) {
|
||||
|
@ -228,11 +275,13 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
|
|||
#endif
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(Scene *scene, ModifierData *md, const int dag_eval_mode)
|
||||
{
|
||||
#if defined(WITH_USD) || defined(WITH_ALEMBIC)
|
||||
MeshSeqCacheModifierData *mcmd = (MeshSeqCacheModifierData *)md;
|
||||
return (mcmd->cache_file != NULL);
|
||||
/* Do not evaluate animations if using the render engine procedural. */
|
||||
return (mcmd->cache_file != NULL) &&
|
||||
!BKE_cache_file_uses_render_procedural(mcmd->cache_file, scene, dag_eval_mode);
|
||||
#else
|
||||
UNUSED_VARS(md);
|
||||
return false;
|
||||
|
|
|
@ -62,7 +62,9 @@ static void deformVerts(ModifierData *UNUSED(md),
|
|||
ctx->depsgraph, scene, ctx->object, DEG_get_ctime(ctx->depsgraph), vertexCos, numVerts);
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -98,7 +98,9 @@ static void freeData(ModifierData *md)
|
|||
}
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ static void foreachTexLink(ModifierData *md, Object *ob, TexWalkFunc walk, void
|
|||
walk(userData, ob, md, "texture");
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene), ModifierData *md, const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md);
|
||||
if (vdmd->texture) {
|
||||
|
|
|
@ -116,7 +116,9 @@ static void matrix_from_obj_pchan(float mat[4][4],
|
|||
}
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *md,
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
WarpModifierData *wmd = (WarpModifierData *)md;
|
||||
|
||||
|
|
|
@ -70,7 +70,9 @@ static void initData(ModifierData *md)
|
|||
MEMCPY_STRUCT_AFTER(wmd, DNA_struct_default_get(WaveModifierData), modifier);
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *UNUSED(md))
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *UNUSED(md),
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -112,7 +112,9 @@ static void requiredDataMask(Object *UNUSED(ob),
|
|||
/* No need to ask for CD_PREVIEW_MLOOPCOL... */
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *md,
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
WeightVGEditModifierData *wmd = (WeightVGEditModifierData *)md;
|
||||
|
||||
|
|
|
@ -154,7 +154,9 @@ static void requiredDataMask(Object *UNUSED(ob),
|
|||
/* No need to ask for CD_PREVIEW_MLOOPCOL... */
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *md,
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
WeightVGMixModifierData *wmd = (WeightVGMixModifierData *)md;
|
||||
|
||||
|
|
|
@ -362,7 +362,9 @@ static void requiredDataMask(Object *UNUSED(ob),
|
|||
/* No need to ask for CD_PREVIEW_MLOOPCOL... */
|
||||
}
|
||||
|
||||
static bool dependsOnTime(ModifierData *md)
|
||||
static bool dependsOnTime(struct Scene *UNUSED(scene),
|
||||
ModifierData *md,
|
||||
const int UNUSED(dag_eval_mode))
|
||||
{
|
||||
WeightVGProximityModifierData *wmd = (WeightVGProximityModifierData *)md;
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ extern "C" {
|
|||
#define RE_USE_GPU_CONTEXT 512
|
||||
#define RE_USE_CUSTOM_FREESTYLE 1024
|
||||
#define RE_USE_NO_IMAGE_SAVE 2048
|
||||
#define RE_USE_ALEMBIC_PROCEDURAL 4096
|
||||
|
||||
/* RenderEngine.flag */
|
||||
#define RE_ENGINE_ANIMATION 1
|
||||
|
@ -235,6 +236,12 @@ void RE_engines_register(RenderEngineType *render_type);
|
|||
|
||||
bool RE_engine_is_opengl(RenderEngineType *render_type);
|
||||
|
||||
/**
|
||||
* Return true if the RenderEngineType has native support for direct loading of Alembic data. For
|
||||
* Cycles, this also checks that the experimental feature set is enabled.
|
||||
*/
|
||||
bool RE_engine_supports_alembic_procedural(const RenderEngineType *render_type, Scene *scene);
|
||||
|
||||
RenderEngineType *RE_engines_find(const char *idname);
|
||||
|
||||
rcti *RE_engine_get_current_tiles(struct Render *re, int *r_total_tiles, bool *r_needs_free);
|
||||
|
|
|
@ -128,6 +128,19 @@ bool RE_engine_is_opengl(RenderEngineType *render_type)
|
|||
return (render_type->draw_engine != NULL) && DRW_engine_render_support(render_type->draw_engine);
|
||||
}
|
||||
|
||||
bool RE_engine_supports_alembic_procedural(const RenderEngineType *render_type, Scene *scene)
|
||||
{
|
||||
if ((render_type->flag & RE_USE_ALEMBIC_PROCEDURAL) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BKE_scene_uses_cycles(scene) && !BKE_scene_uses_cycles_experimental_features(scene)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Create, Free */
|
||||
|
||||
RenderEngine *RE_engine_create(RenderEngineType *type)
|
||||
|
|
Loading…
Reference in New Issue