diff --git a/kchash/nodes/blender_properties.py b/kchash/nodes/blender_properties.py new file mode 100644 index 0000000..6435eb4 --- /dev/null +++ b/kchash/nodes/blender_properties.py @@ -0,0 +1,32 @@ +import bpy + +from bpy.props import ( + FloatProperty, + PointerProperty +) + +class CrayCameraSettings(bpy.types.PropertyGroup): + fov: FloatProperty( + name="Field of View", + description="Field of view of the camera", + min=1.0, max=160.0, + subtype='ANGLE', + default=80.0 + ) + @classmethod + def register(cls): + bpy.types.Camera.c_ray = PointerProperty( + name="c-ray Camera Settings", + description="c-ray Camera Settings", + type=cls + ) + + @classmethod + def unregister(cls): + del bpy.types.Camera.c_ray + +def register(): + bpy.utils.register_class(CrayCameraSettings) + +def unregister(): + bpy.utils.unregister_class(CrayCameraSettings) diff --git a/kchash/nodes/blender_ui.py b/kchash/nodes/blender_ui.py new file mode 100644 index 0000000..0058404 --- /dev/null +++ b/kchash/nodes/blender_ui.py @@ -0,0 +1,279 @@ +import bpy +from bpy.types import Panel + +# Most of this is just a carbon-copy of the Cycles UI boilerplate + +class CrayButtonsPanel: + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + COMPAT_ENGINES = {'C_RAY'} + @classmethod + def poll(cls, context): + return context.engine in cls.COMPAT_ENGINES + +class C_RAY_RENDER_PT_sampling(CrayButtonsPanel, Panel): + bl_label = "Sampling" + + def draw(self, context): + pass + +class C_RAY_RENDER_PT_sampling_render(CrayButtonsPanel, Panel): + bl_label = "Render" + bl_parent_id = "C_RAY_RENDER_PT_sampling" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + heading = layout.column(align=True, heading="Samples") + row = heading.row(align=True) + row.prop(context.scene.c_ray, "samples", text="Samples") + +class C_RAY_RENDER_PT_performance(CrayButtonsPanel, Panel): + bl_label = "Performance" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + pass + +class C_RAY_RENDER_PT_performance_threads(CrayButtonsPanel, Panel): + bl_label = "Threads" + bl_parent_id = "C_RAY_RENDER_PT_performance" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + col = layout.column() + col.prop(context.scene.c_ray, "threads") + +class C_RAY_RENDER_PT_performance_tiling(CrayButtonsPanel, Panel): + bl_label = "Tiling" + bl_parent_id = "C_RAY_RENDER_PT_performance" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + col = layout.column() + col.prop(context.scene.c_ray, "tile_size") + +class C_RAY_RENDER_PT_performance_clustering(CrayButtonsPanel, Panel): + bl_label = "Clustering" + bl_parent_id = "C_RAY_RENDER_PT_performance" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + col = layout.column() + col.prop(context.scene.c_ray, "node_list", text="Worker node list") + +class C_RAY_RENDER_PT_light_paths(CrayButtonsPanel, Panel): + bl_label = "Light Paths" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + pass + +class C_RAY_RENDER_PT_light_paths_bounces(CrayButtonsPanel, Panel): + bl_label = "Bounces" + bl_parent_id = "C_RAY_RENDER_PT_light_paths" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + col = layout.column() + col.prop(context.scene.c_ray, "bounces", text="Total") + +class CRAY_PT_context_material(CrayButtonsPanel, Panel): + bl_label = "" + bl_context = "material" + bl_options = {'HIDE_HEADER'} + + @classmethod + def poll(cls, context): + if context.active_object and context.active_object.type == 'GPENCIL': + return False + else: + return (context.material or context.object) and CrayButtonsPanel.poll(context) + + def draw(self, context): + layout = self.layout + + mat = context.material + ob = context.object + slot = context.material_slot + space = context.space_data + + if ob: + is_sortable = len(ob.material_slots) > 1 + rows = 3 + if (is_sortable): + rows = 4 + + row = layout.row() + + row.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=rows) + + col = row.column(align=True) + col.operator("object.material_slot_add", icon='ADD', text="") + col.operator("object.material_slot_remove", icon='REMOVE', text="") + col.separator() + col.menu("MATERIAL_MT_context_menu", icon='DOWNARROW_HLT', text="") + + if is_sortable: + col.separator() + + col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP' + col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + + if ob.mode == 'EDIT': + row = layout.row(align=True) + row.operator("object.material_slot_assign", text="Assign") + row.operator("object.material_slot_select", text="Select") + row.operator("object.material_slot_deselect", text="Deselect") + + row = layout.row() + + if ob: + row.template_ID(ob, "active_material", new="material.new") + + if slot: + icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA' + row.prop(slot, "link", text="", icon=icon_link, icon_only=True) + + elif mat: + layout.template_ID(space, "pin_id") + layout.separator() + +class C_RAY_MATERIAL_PT_preview(CrayButtonsPanel, Panel): + bl_label = "Preview" + bl_context = "material" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + mat = context.material + return mat and (not mat.grease_pencil) and CrayButtonsPanel.poll(context) + + def draw(self, context): + self.layout.template_preview(context.material) + +class C_RAY_CAMERA_PT_dof(CrayButtonsPanel, Panel): + bl_label = "Depth of Field" + bl_context = "data" + + @classmethod + def poll(cls, context): + return context.camera and CrayButtonsPanel.poll(context) + + def draw_header(self, context): + cam = context.camera + dof = cam.dof + self.layout.prop(dof, "use_dof", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + cam = context.camera + dof = cam.dof + layout.active = dof.use_dof + + split = layout.split() + + col = split.column() + col.prop(dof, "focus_object", text="Focus Object") + if dof.focus_object and dof.focus_object.type == 'ARMATURE': + col.prop_search(dof, "focus_subtarget", dof.focus_object.data, "bones", text="Focus Bone") + sub = col.row() + sub.active = dof.focus_object is None + sub.prop(dof, "focus_distance", text="Distance") + +class C_RAY_CAMERA_PT_dof_aperture(CrayButtonsPanel, Panel): + bl_label = "Aperture" + bl_parent_id = "C_RAY_CAMERA_PT_dof" + + @classmethod + def poll(cls, context): + return context.camera and CrayButtonsPanel.poll(context) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + cam = context.camera + dof = cam.dof + layout.active = dof.use_dof + flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) + + col = flow.column() + col.prop(dof, "aperture_fstop") + +def get_panels(): + exclude_panels = { + 'VIEWLAYER_PT_filter', + 'VIEWLAYER_PT_layer_passes', + } + + panels = [] + for panel in bpy.types.Panel.__subclasses__(): + if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES: + if panel.__name__ not in exclude_panels: + panels.append(panel) + + return panels + +classes = ( + C_RAY_RENDER_PT_sampling, + C_RAY_RENDER_PT_sampling_render, + C_RAY_RENDER_PT_performance, + C_RAY_RENDER_PT_performance_threads, + C_RAY_RENDER_PT_performance_tiling, + C_RAY_RENDER_PT_performance_clustering, + C_RAY_RENDER_PT_light_paths, + C_RAY_RENDER_PT_light_paths_bounces, + CRAY_PT_context_material, + C_RAY_MATERIAL_PT_preview, + C_RAY_CAMERA_PT_dof, + C_RAY_CAMERA_PT_dof_aperture, +) + +def register(): + from bpy.utils import register_class + + from bl_ui import ( + properties_render, + properties_material, + ) + # properties_render.RENDER_PT_render.COMPAT_ENGINES.add(CrayRender.bl_idname) + # properties_material.MATERIAL_PT_preview.COMPAT_ENGINES.add(CrayRender.bl_idname) + for panel in get_panels(): + panel.COMPAT_ENGINES.add('C_RAY') + + for cls in classes: + register_class(cls) + +def unregister(): + from bpy.utils import unregister_class + + from bl_ui import ( + properties_render, + properties_material, + ) + # properties_render.RENDER_PT_render.COMPAT_ENGINES.remove(CrayRender.bl_idname) + # properties_material.MATERIAL_PT_preview.COMPAT_ENGINES.remove(CrayRender.bl_idname) + for panel in get_panels(): + if 'C_RAY' in panel.COMPAT_ENGINES: + panel.COMPAT_ENGINES.remove('C_RAY') + + for cls in classes: + unregister_class(cls) diff --git a/kchash/nodes/c_ray.py b/kchash/nodes/c_ray.py new file mode 100644 index 0000000..7175058 --- /dev/null +++ b/kchash/nodes/c_ray.py @@ -0,0 +1,556 @@ +import ctypes as ct +from contextlib import contextmanager +from enum import IntEnum + +from . import cray_wrap as _lib +from . nodes.vector import ( + cr_vector, + cr_coord +) +from . cray_wrap import ( + CallbackInfo +) + +class cr_renderer(ct.Structure): + pass + +class cr_bm_union(ct.Union): + _fields_ = [ + ("byte_ptr", ct.POINTER(ct.c_ubyte)), + ("float_ptr", ct.POINTER(ct.c_float)) + ] + +class cr_bitmap(ct.Structure): + _fields_ = [ + ("colorspace", ct.c_int), + ("precision", ct.c_int), + ("data", cr_bm_union), + ("stride", ct.c_size_t), + ("width", ct.c_size_t), + ("height", ct.c_size_t), + ] + +class _cr_rparam(IntEnum): + # int + threads = 0 + samples = 1 + bounces = 2 + tile_width = 3 + tile_height = 4 + tile_order = 5 + output_num = 6 + override_width = 7 + override_height = 8 + override_cam = 9 + is_iterative = 10 + # str + output_path = 11 + asset_path = 12 + output_name = 13 + output_filetype = 14 + node_list = 15 + blender_mode = 16 + +def _r_set_num(ptr, param, value): + return _lib.renderer_set_num_pref(ptr, param, value) + +def _r_get_num(ptr, param): + return _lib.renderer_get_num_pref(ptr, param) + +def _r_set_str(ptr, param, value): + return _lib.renderer_set_str_pref(ptr, param, value) + +def _r_get_str(ptr, param): + return _lib.renderer_get_str_pref(ptr, param) + +class _cr_cb_type(IntEnum): + on_start = 0, + on_stop = 1, + on_status_update = 2, + on_state_changed = 3, # Not connected currently, c-ray never calls this + on_interactive_pass_finished = 4 + +class _callbacks: + def __init__(self, r_ptr): + self.r_ptr = r_ptr + + def _set_on_start(self, fn_and_userdata): + fn, user_data = fn_and_userdata + if not callable(fn): + raise TypeError("on_start callback function not callable") + _lib.renderer_set_callback(self.r_ptr, _cr_cb_type.on_start, fn, user_data) + on_start = property(None, _set_on_start, None, "Tuple (fn,user_data) - fn will be called when c-ray starts rendering, with arguments (cr_cb_info, user_data)") + + def _set_on_stop(self, fn_and_userdata): + fn, user_data = fn_and_userdata + if not callable(fn): + raise TypeError("on_stop callback function not callable") + _lib.renderer_set_callback(self.r_ptr, _cr_cb_type.on_stop, fn, user_data) + on_stop = property(None, _set_on_stop, None, "Tuple (fn,user_data) - fn will be called when c-ray is done rendering, with arguments (cr_cb_info, user_data)") + + def _set_on_status_update(self, fn_and_userdata): + fn, user_data = fn_and_userdata + if not callable(fn): + raise TypeError("on_status_update callback function not callable") + _lib.renderer_set_callback(self.r_ptr, _cr_cb_type.on_status_update, fn, user_data) + on_status_update = property(None, _set_on_status_update, None, "Tuple (fn,user_data) - fn will be called periodically while c-ray is rendering, with arguments (cr_cb_info, user_data)") + + def _set_on_interactive_pass_finished(self, fn_and_userdata): + fn, user_data = fn_and_userdata + if not callable(fn): + raise TypeError("on_interactive_pass_finished callback function not callable") + _lib.renderer_set_callback(self.r_ptr, _cr_cb_type.on_interactive_pass_finished, fn, user_data) + on_interactive_pass_finished = property(None, _set_on_interactive_pass_finished, None, "Tuple (fn,user_data) - fn will be called every time c-ray finishes rendering a pass in interactive mode, with arguments (cr_cb_info, user_data)") + +class _pref: + def __init__(self, r_ptr): + self.r_ptr = r_ptr + + def _get_threads(self): + return _r_get_num(self.r_ptr, _cr_rparam.threads) + def _set_threads(self, value): + _r_set_num(self.r_ptr, _cr_rparam.threads, value) + threads = property(_get_threads, _set_threads, None, "Local thread count, defaults to nproc + 2") + + def _get_samples(self): + return _r_get_num(self.r_ptr, _cr_rparam.samples) + def _set_samples(self, value): + _r_set_num(self.r_ptr, _cr_rparam.samples, value) + samples = property(_get_samples, _set_samples, None, "Amount of samples to render") + + def _get_bounces(self): + return _r_get_num(self.r_ptr, _cr_rparam.bounces) + def _set_bounces(self, value): + _r_set_num(self.r_ptr, _cr_rparam.bounces, value) + bounces = property(_get_bounces, _set_bounces, None, "Max times a light ray can bounce in the scene") + + def _get_tile_x(self): + return _r_get_num(self.r_ptr, _cr_rparam.tile_width) + def _set_tile_x(self, value): + _r_set_num(self.r_ptr, _cr_rparam.tile_width, value) + tile_x = property(_get_tile_x, _set_tile_x, None, "Tile width") + + def _get_tile_y(self): + return _r_get_num(self.r_ptr, _cr_rparam.tile_height) + def _set_tile_y(self, value): + _r_set_num(self.r_ptr, _cr_rparam.tile_height, value) + tile_y = property(_get_tile_y, _set_tile_y, None, "Tile height") + + def _get_tile_order(self): + return _r_get_num(self.r_ptr, _cr_rparam.tile_order) + def _set_tile_order(self, value): + _r_set_num(self.r_ptr, _cr_rparam.tile_order, value) + tile_order = property(_get_tile_order, _set_tile_order, None, "Order to render tiles in") + + def _get_output_idx(self): + return _r_get_num(self.r_ptr, _cr_rparam.output_num) + def _set_output_idx(self, value): + _r_set_num(self.r_ptr, _cr_rparam.output_num, value) + output_idx = property(_get_output_idx, _set_output_idx, None, "Number for output file name") + + def _get_img_width(self): + return _r_get_num(self.r_ptr, _cr_rparam.override_width) + def _set_img_width(self, value): + _r_set_num(self.r_ptr, _cr_rparam.override_width, value) + img_width = property(_get_img_width, _set_img_width, None, "Image width in pixels") + + def _get_img_height(self): + return _r_get_num(self.r_ptr, _cr_rparam.override_height) + def _set_img_height(self, value): + _r_set_num(self.r_ptr, _cr_rparam.override_height, value) + img_height = property(_get_img_height, _set_img_height, None, "Image height in pixels") + + def _get_should_save(self): + return _r_get_num(self.r_ptr, _cr_rparam.should_save) + def _set_should_save(self, value): + _r_set_num(self.r_ptr, _cr_rparam.should_save, value) + should_save = property(_get_should_save, _set_should_save, None, "0 = don't save, 1 = save") + + def _get_cam_idx(self): + return _r_get_num(self.r_ptr, _cr_rparam.override_cam) + def _set_cam_idx(self, value): + _r_set_num(self.r_ptr, _cr_rparam.override_cam, value) + cam_idx = property(_get_cam_idx, _set_cam_idx, None, "Select camera") + + def _get_is_iterative(self): + return _r_get_num(self.r_ptr, _cr_rparam.is_iterative) + def _set_is_iterative(self, value): + _r_set_num(self.r_ptr, _cr_rparam.is_iterative, value) + is_iterative = property(_get_is_iterative, _set_is_iterative, None, "") + + def _get_output_path(self): + return _r_get_str(self.r_ptr, _cr_rparam.output_path) + def _set_output_path(self, value): + _r_set_str(self.r_ptr, _cr_rparam.output_path, value) + output_path = property(_get_output_path, _set_output_path, None, "") + + def _get_asset_path(self): + return _r_get_str(self.r_ptr, _cr_rparam.asset_path) + def _set_asset_path(self, value): + _r_set_str(self.r_ptr, _cr_rparam.asset_path, value) + asset_path = property(_get_asset_path, _set_asset_path, None, "") + + def _get_output_name(self): + return _r_get_str(self.r_ptr, _cr_rparam.output_name) + def _set_output_name(self, value): + _r_set_str(self.r_ptr, _cr_rparam.output_name, value) + output_name = property(_get_output_name, _set_output_name, None, "") + + def _get_output_filetype(self): + return _r_get_str(self.r_ptr, _cr_rparam.output_filetype) + def _set_output_filetype(self, value): + _r_set_str(self.r_ptr, _cr_rparam.output_filetype, value) + output_filetype = property(_get_output_filetype, _set_output_filetype, None, "") + + def _get_node_list(self): + return _r_get_str(self.r_ptr, _cr_rparam.node_list) + def _set_node_list(self, value): + _r_set_str(self.r_ptr, _cr_rparam.node_list, value) + node_list = property(_get_node_list, _set_node_list, None, "") + + def _get_blender_mode(self): + return _r_get_num(self.r_ptr, _cr_rparam.blender_mode) + def _set_blender_mode(self, value): + _r_set_num(self.r_ptr, _cr_rparam.blender_mode, value) + blender_mode = property(_get_blender_mode, _set_blender_mode, None, "") + +class _version: + def _get_semantic(self): + return _lib.get_version() + semantic = property(_get_semantic, None, None, "") + def _get_hash(self): + return _lib.get_git_hash() + githash = property(_get_hash, None, None, "") + +version = _version() + +threeint = ct.c_int * 3 + +class cr_face(ct.Structure): + _fields_ = [ + ("vertex_idx", threeint), + ("normal_idx", threeint), + ("texture_idx", threeint), + ("mat_idx", ct.c_uint, 16), + ("has_normals", ct.c_bool, 1) + ] + +class mesh: + def __init__(self, scene_ptr, name): + self.scene_ptr = scene_ptr + self.name = name + self.instances = [] + self.cr_idx = _lib.scene_mesh_new(self.scene_ptr, self.name) + + def bind_vertex_buf(self, buf): + _lib.mesh_bind_vertex_buf(self.scene_ptr, self.cr_idx, buf.cr_idx) + def bind_faces(self, faces, face_count): + _lib.mesh_bind_faces(self.scene_ptr, self.cr_idx, faces, face_count) + def instance_new(self): + self.instances.append(instance(self.scene_ptr, self, 0)) + return self.instances[-1] + +class sphere: + def __init__(self, scene_ptr, radius): + self.scene_ptr = scene_ptr + self.radius = radius + self.cr_idx = _lib.scene_add_sphere(self.scene_ptr, self.radius) + +class _cam_param(IntEnum): + fov = 0 + focus_distance = 1 + fstops = 2 + pose_x = 3 + pose_y = 4 + pose_z = 5 + pose_roll = 6 + pose_pitch = 7 + pose_yaw = 8 + time = 9 + res_x = 10 + res_y = 11 + blender_coord = 12 + +def _cam_get_num(scene_ptr, cam_idx, param): + return _lib.camera_get_num_pref(scene_ptr, cam_idx, param) + +def _cam_set_num(scene_ptr, cam_idx, param, value): + _lib.camera_set_num_pref(scene_ptr, cam_idx, param, value) + # Weird. Could just do this internally, no? + _lib.camera_update(scene_ptr, cam_idx) + +class _cam_pref: + def __init__(self, scene_ptr, cam_idx): + self.scene_ptr = scene_ptr + self.cam_idx = cam_idx + + def _get_fov(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.fov) + def _set_fov(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.fov, value) + fov = property(_get_fov, _set_fov, None, "Camera field of view, in radians") + + def _get_focus_distance(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.focus_distance) + def _set_focus_distance(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.focus_distance, value) + focus_distance = property(_get_focus_distance, _set_focus_distance, None, "Camera focus distance, in meters") + + def _get_fstops(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.fstops) + def _set_fstops(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.fstops, value) + fstops = property(_get_fstops, _set_fstops, None, "Camera aperture, in f-stops") + + def _get_pose_x(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.pose_x) + def _set_pose_x(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.pose_x, value) + pose_x = property(_get_pose_x, _set_pose_x, None, "Camera x coordinate in world space") + + def _get_pose_y(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.pose_y) + def _set_pose_y(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.pose_y, value) + pose_y = property(_get_pose_y, _set_pose_y, None, "Camera y coordinate in world space") + + def _get_pose_z(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.pose_z) + def _set_pose_z(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.pose_z, value) + pose_z = property(_get_pose_z, _set_pose_z, None, "Camera z coordinate in world space") + + def _get_pose_roll(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.pose_roll) + def _set_pose_roll(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.pose_roll, value) + pose_roll = property(_get_pose_roll, _set_pose_roll, None, "Camera roll, in radians") + + def _get_pose_pitch(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.pose_pitch) + def _set_pose_pitch(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.pose_pitch, value) + pose_pitch = property(_get_pose_pitch, _set_pose_pitch, None, "Camera pitch, in radians") + + def _get_pose_yaw(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.pose_yaw) + def _set_pose_yaw(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.pose_yaw, value) + pose_yaw = property(_get_pose_yaw, _set_pose_yaw, None, "Camera yaw, in radians") + + def _get_time(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.time) + def _set_time(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.time, value) + time = property(_get_time, _set_time, None, "Camera animation t") + + def _get_res_x(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.res_x) + def _set_res_x(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.res_x, value) + res_x = property(_get_res_x, _set_res_x, None, "Camera x resolution, in pixels") + def _get_res_y(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.res_y) + def _set_res_y(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.res_y, value) + res_y = property(_get_res_y, _set_res_y, None, "Camera y resolution, in pixels") + def _get_blender_coord(self): + return _cam_get_num(self.scene_ptr, self.cam_idx, _cam_param.blender_coord) + def _set_blender_coord(self, value): + return _cam_set_num(self.scene_ptr, self.cam_idx, _cam_param.blender_coord, value) + blender_coord = property(_get_blender_coord, _set_blender_coord, None, "Boolean toggle to use Blender coordinate system in c-ray") +class camera: + def __init__(self, scene_ptr): + self.scene_ptr = scene_ptr + self.cr_idx = _lib.camera_new(self.scene_ptr) + self.opts = _cam_pref(self.scene_ptr, self.cr_idx) + self.params = {} + +def inst_type(IntEnum): + mesh = 0 + sphere = 1 + +class cr_matrix(ct.Structure): + _fields_ = [ + ("mtx", (ct.c_float * 16)) + ] + +class instance: + def __init__(self, scene_ptr, object, type): + self.scene_ptr = scene_ptr + self.object = object + self.type = type + self.material_set = None + self.matrix = None + self.cr_idx = _lib.instance_new(self.scene_ptr, self.object.cr_idx, self.type) + + def set_transform(self, matrix): + self.matrix = matrix + _lib.instance_set_transform(self.scene_ptr, self.cr_idx, self.matrix) + + def transform(self, matrix): + # TODO: Figure out matmul in python + # self.matrix = self.matrix * matrix + _lib.instance_set_transform(self.scene_ptr, self.cr_idx, self.matrix) + + def bind_materials(self, material_set): + self.material_set = material_set + _lib.instance_bind_material_set(self.scene_ptr, self.cr_idx, self.material_set.cr_idx) + +class vertex_buf: + def __init__(self, scene_ptr, v, vn, n, nn, t, tn): + self.cr_ptr = scene_ptr + self.v = v + self.vn = vn + self.n = n + self.nn = nn + self.t = t + self.tn = tn + self.cr_idx = _lib.scene_vertex_buf_new(self.cr_ptr, self.v, self.vn, self.n, self.nn, self.t, self.tn) + +class material_set: + def __init__(self, scene_ptr): + self.scene_ptr = scene_ptr + self.materials = [] + self.cr_idx = _lib.scene_new_material_set(self.scene_ptr) + + def add(self, material): + if material is None: + _lib.material_set_add(self.scene_ptr, self.cr_idx, None) + return + self.materials.append(material) + ct.pythonapi.PyCapsule_New.argtypes = [ct.c_void_p, ct.c_char_p, ct.c_void_p] + ct.pythonapi.PyCapsule_New.restype = ct.py_object + name = b'cray.shader_node' + capsule = ct.pythonapi.PyCapsule_New(ct.byref(material.cr_struct), name, None) + _lib.material_set_add(self.scene_ptr, self.cr_idx, capsule) + +class scene: + def __init__(self, cr_renderer): + self.cr_renderer = cr_renderer + self.cr_ptr = _lib.renderer_scene_get(self.cr_renderer) + self.meshes = {} + self.cameras = {} + def close(self): + del(self.s_ptr) + + def totals(self): + return _lib.scene_totals(self.cr_ptr) + def mesh_new(self, name): + self.meshes[name] = mesh(self.cr_ptr, name) + return self.meshes[name] + def sphere_new(self, radius): + return sphere(self.cr_ptr, radius) + def camera_new(self, name): + self.cameras[name] = camera(self.cr_ptr) + return self.cameras[name] + def material_set_new(self): + return material_set(self.cr_ptr) + def set_background(self, material): + if material is None: + return _lib.scene_set_background(self.cr_ptr, material) + self.background = material + ct.pythonapi.PyCapsule_New.argtypes = [ct.c_void_p, ct.c_char_p, ct.c_void_p] + ct.pythonapi.PyCapsule_New.restype = ct.py_object + name = b'cray.shader_node' + capsule = ct.pythonapi.PyCapsule_New(ct.byref(material.cr_struct), name, None) + return _lib.scene_set_background(self.cr_ptr, capsule) + def vertex_buf_new(self, v, vn, n, nn, t, tn): + return vertex_buf(self.cr_ptr, v, vn, n, nn, t, tn) + +class cr_cb_info(ct.Structure): + _fields_ = [ + ("fb", ct.POINTER(cr_bitmap)), + ("tiles", ct.POINTER(ct.c_void_p)), # TODO + ("tiles_count", ct.c_size_t), + ("active_threads", ct.c_size_t), + ("avg_per_ray_us", ct.c_double), + ("samples_per_sec", ct.c_int64), + ("eta_ms", ct.c_int64), + ("finished_passes", ct.c_size_t), + ("completion", ct.c_double), + ("paused", ct.c_bool), + ] + +cr_cb_func = ct.CFUNCTYPE(ct.c_void_p, ct.POINTER(cr_cb_info), ct.POINTER(ct.c_void_p)) + +class renderer: + def __init__(self, path = None): + self.obj_ptr = _lib.new_renderer() + self.prefs = _pref(self.obj_ptr) + self.callbacks = _callbacks(self.obj_ptr) + self.interactive = False + if path != None: + _lib.load_json(self.obj_ptr, path) + + def close(self): + _lib.renderer_destroy(self.obj_ptr) + del(self.obj_ptr) + + def stop(self): + self.interactive = False + _lib.renderer_stop(self.obj_ptr) + + def restart(self): + _lib.renderer_restart(self.obj_ptr) + + def toggle_pause(): + _lib.renderer_toggle_pause(self.obj_ptr) + + def render(self): + _lib.renderer_render(self.obj_ptr) + + def start_interactive(self): + self.interactive = True + _lib.renderer_start_interactive(self.obj_ptr) + + def get_result(self): + ret = _lib.renderer_get_result(self.obj_ptr) + if not ret: + return None + # I saw this in several places, and suggested by a token predictor. Ehh? + ct.pythonapi.PyCapsule_GetPointer.restype = ct.c_void_p + ct.pythonapi.PyCapsule_GetPointer.argtypes = [ct.py_object, ct.c_char_p] + ptr = ct.pythonapi.PyCapsule_GetPointer(ret, "cray.cr_bitmap".encode()) + ret_bitmap = ct.cast(ptr, ct.POINTER(cr_bitmap)).contents + return ret_bitmap + + def scene_get(self): + return scene(self.obj_ptr) + + def debug_dump(self): + _lib.debug_dump_state(self.obj_ptr) + + @classmethod + def from_param(cls, param): + if not isinstance(param, cls): + raise TypeError("Expected an instance of Renderer") + return param.obj_ptr + + @contextmanager + def __call__(self): + yield self + self.close() + def __enter__(self): + return self + def __exit__(self, exc_type, exc_value, traceback): + self.close() + +def start_render_worker(port, thread_limit): + _lib.start_render_worker(port, thread_limit) + +def send_shutdown_to_workers(node_list): + _lib.send_shutdown_to_workers(node_list) + + +class log_level(IntEnum): + Silent = 0 + Info = 1 + Debug = 2 + Spam = 3 + +def log_level_set(level): + _lib.log_level_set(level) + +def log_level_get(): + return _lib.log_level_get() diff --git a/kchash/nodes/cray_wrap.c b/kchash/nodes/cray_wrap.c new file mode 100644 index 0000000..9250f0f --- /dev/null +++ b/kchash/nodes/cray_wrap.c @@ -0,0 +1,776 @@ +// +// cray.c +// libc-ray CPython wrapper +// +// Created by Valtteri on 5.12.2023. +// Copyright © 2023 Valtteri Koskivuori. All rights reserved. +// + +#include +#include +#include + +static PyObject *py_cr_get_version(PyObject *self, PyObject *args) { + (void)self; (void)args; + return PyUnicode_FromString(cr_get_version()); +} + +static PyObject *py_cr_get_git_hash(PyObject *self, PyObject *args) { + (void)self; (void)args; + return PyUnicode_FromString(cr_get_git_hash()); +} + +static PyObject *py_cr_new_renderer(PyObject *self, PyObject *args) { + (void)self; (void)args; + struct cr_renderer *new = cr_new_renderer(); + if (!new) { + PyErr_SetString(PyExc_MemoryError, "Failed to allocate renderer"); + return NULL; + } + return PyCapsule_New(new, "cray.cr_renderer", NULL); +} + +static PyObject *py_cr_destroy_renderer(PyObject *self, PyObject *args) { + (void)self; + PyObject *r_ext; + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + cr_destroy_renderer(r); + Py_RETURN_NONE; +} + +static PyObject *py_cr_renderer_set_num_pref(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + enum cr_renderer_param p; + uint64_t num; + + if (!PyArg_ParseTuple(args, "OIk", &r_ext, &p, &num)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + bool ret = cr_renderer_set_num_pref(r, p, num); + return PyBool_FromLong(ret); +} + +static PyObject *py_cr_renderer_set_str_pref(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + enum cr_renderer_param p; + char *str; + + if (!PyArg_ParseTuple(args, "OIs", &r_ext, &p, &str)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + bool ret = cr_renderer_set_str_pref(r, p, str); + return PyBool_FromLong(ret); +} + + +typedef struct { + PyObject_HEAD + struct cr_renderer_cb_info info; +} CallbackInfoObject; + +static PyMemberDef CallbackInfo_members[] = { + { "tiles_count", T_ULONG, offsetof(CallbackInfoObject, info.tiles_count), 0, "Amount of tiles" }, + { "active_threads", T_ULONG, offsetof(CallbackInfoObject, info.active_threads), 0, "Amount of active threads" }, + { "avg_per_ray", T_DOUBLE, offsetof(CallbackInfoObject, info.avg_per_ray_us), 0, "Microseconds per ray, on average" }, + { "samples_per_sec", T_LONG, offsetof(CallbackInfoObject, info.samples_per_sec), 0, "Samples per second" }, + { "eta_ms", T_LONG, offsetof(CallbackInfoObject, info.eta_ms), 0, "ETA to render finished, in milliseconds" }, + { "finished_passes", T_ULONG, offsetof(CallbackInfoObject, info.finished_passes), 0, "Passes finished in interactive mode" }, + { "completion", T_DOUBLE, offsetof(CallbackInfoObject, info.completion), 0, "Render completion" }, + { "paused", T_INT, offsetof(CallbackInfoObject, info.paused), 0, "Boolean, render paused" }, + { NULL }, +}; + +static PyTypeObject CallbackInfoType = { + .ob_base = PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "c_ray.CallbackInfo", + .tp_doc = PyDoc_STR(""), + .tp_basicsize = sizeof(CallbackInfoObject), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_members = CallbackInfo_members, +}; + +void py_callable_wrapper(struct cr_renderer_cb_info *info, void *arg) { + PyObject *py_callback_fn = NULL; + PyObject *py_user_data = NULL; + PyGILState_STATE state = PyGILState_Ensure(); + if (!PyArg_ParseTuple(arg, "OO", &py_callback_fn, &py_user_data)) { + printf("Failed to parse args in py_callable_wrapper\n"); + return; + } + PyObject *args = Py_BuildValue("()"); + CallbackInfoObject *cb = (CallbackInfoObject *)CallbackInfoType.tp_new(&CallbackInfoType, args, NULL); + Py_DECREF(args); + cb->info = *info; + PyObject *py_args = Py_BuildValue("(OO)", cb, py_user_data); + if (!py_args) { + printf("In py_callable_wrapper: "); + PyErr_Print(); + return; + } + Py_DECREF(cb); + Py_INCREF(py_args); + PyObject *result = PyObject_Call(py_callback_fn, py_args, NULL); + if (result) Py_DECREF(result); + Py_DECREF(py_args); + PyGILState_Release(state); +} + +static PyObject *py_cr_renderer_set_callback(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + enum cr_renderer_callback callback_type; + PyObject *py_callback_fn; + PyObject *py_user_data = NULL; + + if (!PyArg_ParseTuple(args, "OIO|O", &r_ext, &callback_type, &py_callback_fn, &py_user_data)) { + return NULL; + } + if (callback_type > cr_cb_on_interactive_pass_finished) { + PyErr_SetString(PyExc_ValueError, "Unknown callback type"); + return NULL; + } + if (!PyCallable_Check(py_callback_fn)) { + PyErr_SetString(PyExc_ValueError, "callback must be callable"); + return NULL; + } + + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + PyObject *py_arg = Py_BuildValue("(OO)", py_callback_fn, py_user_data); + if (!py_arg) { + printf("In py_cr_renderer_set_callback: "); + PyErr_Print(); + return NULL; + } + Py_INCREF(py_arg); + bool ret = cr_renderer_set_callback(r, callback_type, py_callable_wrapper, py_arg); + return PyBool_FromLong(ret); +} + +static PyObject *py_cr_renderer_stop(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + Py_BEGIN_ALLOW_THREADS + cr_renderer_stop(r); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + +static PyObject *py_cr_renderer_restart(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + // Unsure why, but we get a deadlock if we're restarting the renderer while it's calling + // the Python callback to report a pass was finished and we don't wrap this call with these + // begin/allow macros. Adding them fixed the deadlock, so I'm happy. + Py_BEGIN_ALLOW_THREADS; + cr_renderer_restart_interactive(r); + Py_END_ALLOW_THREADS; + Py_RETURN_NONE; +} + +static PyObject *py_cr_renderer_toggle_pause(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + cr_renderer_toggle_pause(r); + Py_RETURN_NONE; +} + +static PyObject *py_cr_renderer_get_str_pref(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + enum cr_renderer_param p; + if (!PyArg_ParseTuple(args, "OI", &r_ext, &p)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + const char *ret = cr_renderer_get_str_pref(r, p); + if (!ret) { + PyErr_SetString(PyExc_ValueError, "cr_renderer_param not a string type"); + return NULL; + } + return PyUnicode_FromString(ret); +} + +static PyObject *py_cr_renderer_get_num_pref(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + enum cr_renderer_param p; + if (!PyArg_ParseTuple(args, "OI", &r_ext, &p)) { + return NULL; + } + if (p > cr_renderer_is_iterative) { + PyErr_SetString(PyExc_ValueError, "cr_renderer_param not a number type"); + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + uint64_t ret = cr_renderer_get_num_pref(r, p); + return PyLong_FromUnsignedLong(ret); +} + +static PyObject *py_cr_renderer_get_result(PyObject *self, PyObject *args) { + (void)self; + PyObject *r_ext; + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + struct cr_bitmap *bm = cr_renderer_get_result(r); + if (!bm) Py_RETURN_NONE; + return PyCapsule_New(bm, "cray.cr_bitmap", NULL); +} + +static PyObject *py_cr_renderer_render(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + Py_BEGIN_ALLOW_THREADS + cr_renderer_render(r); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + +static PyObject *py_cr_renderer_start_interactive(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + cr_renderer_start_interactive(r); + Py_RETURN_NONE; +} + +// TODO: Same here, not sure if we want an explicit teardown +// static PyObject *py_cr_bitmap_free(PyObject *self, PyObject *args) { +// Py_RETURN_NOTIMPLEMENTED; +// } + +static PyObject *py_cr_renderer_scene_get(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *r_ext; + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + struct cr_scene *s = cr_renderer_scene_get(r); + return PyCapsule_New(s, "cray.cr_scene", NULL); +} + +static PyObject *py_cr_scene_totals(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + if (!PyArg_ParseTuple(args, "O", &s_ext)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + struct cr_scene_totals totals = cr_scene_totals(s); + return Py_BuildValue( + "{s:i, s:i, s:i, s:i}", + "meshes", totals.meshes, + "spheres", totals.spheres, + "instances", totals.instances, + "cameras", totals.cameras); +} + +static PyObject *py_cr_scene_add_sphere(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + float radius; + if (!PyArg_ParseTuple(args, "Of", &s_ext, &radius)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_sphere sphere = cr_scene_add_sphere(s, radius); + return PyLong_FromLong(sphere); +} + +static PyObject *py_cr_scene_vertex_buf_new(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + PyObject *vec_buff; + size_t vec_count; + PyObject *nor_buff; + size_t nor_count; + PyObject *tex_buff; + size_t tex_count; + if (!PyArg_ParseTuple(args, "OOnOnOn", &s_ext, &vec_buff, &vec_count, &nor_buff, &nor_count, &tex_buff, &tex_count)) { + return NULL; + } + Py_buffer vec_view; + if (PyObject_GetBuffer(vec_buff, &vec_view, PyBUF_C_CONTIGUOUS | PyBUF_FORMAT) == -1) { + PyErr_SetString(PyExc_MemoryError, "Failed to parse vec_view"); + return NULL; + } + if (vec_count && (vec_view.len / sizeof(struct cr_vector)) != vec_count) { + printf("vec_count: %zu\n", vec_count); + printf("vec_view.len: %zu\n", vec_view.len); + PyErr_SetString(PyExc_MemoryError, "vec_view / sizeof(struct cr_vector) != vec_count"); + return NULL; + } + + Py_buffer nor_view; + if (PyObject_GetBuffer(nor_buff, &nor_view, PyBUF_C_CONTIGUOUS | PyBUF_FORMAT) == -1) { + PyErr_SetString(PyExc_MemoryError, "Failed to parse nor_view"); + return NULL; + } + if (nor_count && (nor_view.len / sizeof(struct cr_vector)) != nor_count) { + printf("nor_count: %zu\n", nor_count); + printf("nor_view.len: %zu\n", nor_view.len); + PyErr_SetString(PyExc_MemoryError, "nor_view / sizeof(struct cr_vector) != nor_count"); + return NULL; + } + + Py_buffer tex_view; + if (PyObject_GetBuffer(tex_buff, &tex_view, PyBUF_C_CONTIGUOUS | PyBUF_FORMAT) == -1) { + PyErr_SetString(PyExc_MemoryError, "Failed to parse tex_view"); + return NULL; + } + if (tex_count && (tex_view.len / sizeof(struct cr_coord)) != tex_count) { + printf("tex_count: %zu\n", tex_count); + printf("tex_view.len: %zu\n", tex_view.len); + PyErr_SetString(PyExc_MemoryError, "tex_view / sizeof(struct cr_coord) != tex_count"); + return NULL; + } + + // TODO: add checks + struct cr_vector *vertices = calloc(vec_count, sizeof(*vertices)); + struct cr_vector *normals = calloc(nor_count, sizeof(*normals)); + struct cr_coord *texcoords = calloc(tex_count, sizeof(*texcoords)); + memcpy(vertices, vec_view.buf, vec_count * sizeof(*vertices)); + memcpy(normals, nor_view.buf, nor_count * sizeof(*normals)); + memcpy(texcoords, tex_view.buf, tex_count * sizeof(*texcoords)); + + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_vertex_buf buf = cr_scene_vertex_buf_new(s, (struct cr_vertex_buf_param){ + .vertices = vertices, + .vertex_count = vec_count, + .normals = normals, + .normal_count = nor_count, + .tex_coords = texcoords, + .tex_coord_count = tex_count + }); + + free(vertices); + free(normals); + free(texcoords); + return PyLong_FromLong(buf); +} + +static PyObject *py_cr_mesh_bind_vertex_buf(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_mesh mesh; + cr_vertex_buf vbuf; + if (!PyArg_ParseTuple(args, "Oll", &s_ext, &mesh, &vbuf)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_mesh_bind_vertex_buf(s, mesh, vbuf); + Py_RETURN_NONE; +} + +static void dump_face(struct cr_face f) { + printf("v0: %i, v1: %i, v2: %i\nn0: %i, n1: %i, n2: %i\nt0: %i, t1: %i, t2: %i\nmat_id: %i, has_normals: %i\n", + f.vertex_idx[0], f.vertex_idx[1], f.vertex_idx[2], + f.normal_idx[0], f.normal_idx[1], f.normal_idx[2], + f.texture_idx[0], f.texture_idx[1], f.texture_idx[2], + f.mat_idx, + f.has_normals == 1 ? 1 : 0); +} + +static PyObject *py_cr_mesh_bind_faces(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_mesh mesh; + PyObject *face_buff; + Py_buffer face_view; + size_t face_count; + if (!PyArg_ParseTuple(args, "OlOn", &s_ext, &mesh, &face_buff, &face_count)) { + return NULL; + } + if (PyObject_GetBuffer(face_buff, &face_view, PyBUF_C_CONTIGUOUS | PyBUF_FORMAT) == -1) { + return NULL; + } + + if ((face_view.len / sizeof(struct cr_face)) != face_count) { + printf("face_count: %zu\n", face_count); + PyErr_SetString(PyExc_MemoryError, "face_view / sizeof(struct cr_face) != face_count"); + return NULL; + } + + struct cr_face *faces = calloc(face_count, sizeof(*faces)); + memcpy(faces, face_view.buf, face_count * sizeof(*faces)); + // for (size_t i = 0; i < face_count; ++i) { + // dump_face(faces[i]); + // } + + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_mesh_bind_faces(s, mesh, faces, face_count); + free(faces); + Py_RETURN_NONE; +} + +static PyObject *py_cr_scene_mesh_new(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + char *name; + if (!PyArg_ParseTuple(args, "Os", &s_ext, &name)) { + return NULL; + } + if (!name) { + PyErr_SetString(PyExc_ValueError, "Name can't be empty"); + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_mesh mesh = cr_scene_mesh_new(s, name); + return PyLong_FromLong(mesh); +} + +static PyObject *py_cr_scene_get_mesh(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + char *name; + if (!PyArg_ParseTuple(args, "Os", &s_ext, &name)) { + return NULL; + } + if (!name) { + PyErr_SetString(PyExc_ValueError, "Name can't be empty"); + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_mesh mesh = cr_scene_get_mesh(s, name); + return PyLong_FromLong(mesh); +} + +static PyObject *py_cr_camera_new(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + if (!PyArg_ParseTuple(args, "O", &s_ext)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_camera new = cr_camera_new(s); + return PyLong_FromLong(new); +} + +static PyObject *py_cr_camera_set_num_pref(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_camera cam; + enum cr_camera_param param; + double num; + if (!PyArg_ParseTuple(args, "Olid", &s_ext, &cam, ¶m, &num)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + bool ret = cr_camera_set_num_pref(s, cam, param, num); + return PyBool_FromLong(ret); +} + +static PyObject *py_cr_camera_get_num_pref(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_camera cam; + enum cr_camera_param param; + if (!PyArg_ParseTuple(args, "Oli", &s_ext, &cam, ¶m)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + double value = cr_camera_get_num_pref(s, cam, param); + return PyLong_FromDouble(value); +} + +static PyObject *py_cr_camera_update(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_camera cam; + if (!PyArg_ParseTuple(args, "Ol", &s_ext, &cam)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + bool ret = cr_camera_update(s, cam); + return PyBool_FromLong(ret); +} + +static PyObject *py_cr_scene_new_material_set(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + if (!PyArg_ParseTuple(args, "O", &s_ext)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_material_set new = cr_scene_new_material_set(s); + return PyLong_FromLong(new); +} + +static PyObject *py_cr_material_set_add(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_material_set set; + PyObject *node_desc; + if (!PyArg_ParseTuple(args, "OlO", &s_ext, &set, &node_desc)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + if (!PyCapsule_IsValid(node_desc, "cray.shader_node")) { + cr_material_set_add(s, set, NULL); + Py_RETURN_NONE; + } + struct cr_shader_node *desc = PyCapsule_GetPointer(node_desc, "cray.shader_node"); + cr_material_set_add(s, set, desc); + Py_RETURN_NONE; +} + +static PyObject *py_cr_instance_new(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_object object; + enum cr_object_type type; + if (!PyArg_ParseTuple(args, "OlI", &s_ext, &object, &type)) { + return NULL; + } + if (type != cr_object_mesh && type != cr_object_sphere) { + PyErr_SetString(PyExc_ValueError, "Unknown cr_object_type"); + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_instance new = cr_instance_new(s, object, type); + return PyLong_FromLong(new); +} + +static PyObject *py_cr_instance_set_transform(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_instance instance; + PyObject *mtx_buff; + Py_buffer mtx_view; + if (!PyArg_ParseTuple(args, "OlO", &s_ext, &instance, &mtx_buff)) { + return NULL; + } + if (PyObject_GetBuffer(mtx_buff, &mtx_view, PyBUF_C_CONTIGUOUS | PyBUF_FORMAT) == -1) { + return NULL; + } + if ((mtx_view.len / sizeof(float)) != 4 * 4) { + PyErr_SetString(PyExc_MemoryError, "mtx_view / sizeof(float) != 4*4"); + return NULL; + } + float mtx[4][4]; + memcpy(mtx, mtx_view.buf, 4 * 4 * sizeof(float)); + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_instance_set_transform(s, instance, mtx); + Py_RETURN_NONE; +} + +static PyObject *py_cr_instance_transform(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_instance instance; + PyObject *mtx_buff; + Py_buffer mtx_view; + if (!PyArg_ParseTuple(args, "OlO", &s_ext, &instance, &mtx_buff)) { + return NULL; + } + if (PyObject_GetBuffer(mtx_buff, &mtx_view, PyBUF_C_CONTIGUOUS | PyBUF_FORMAT) == -1) { + return NULL; + } + if ((mtx_view.len / sizeof(float)) != 4 * 4) { + PyErr_SetString(PyExc_MemoryError, "mtx_view / sizeof(float) != 4*4"); + return NULL; + } + float mtx[4][4]; + memcpy(mtx, mtx_view.buf, 4 * 4 * sizeof(float)); + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + cr_instance_transform(s, instance, mtx); + Py_RETURN_NONE; +} + +static PyObject *py_cr_instance_bind_material_set(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + cr_instance instance; + cr_material_set set; + if (!PyArg_ParseTuple(args, "Oll", &s_ext, &instance, &set)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + bool ret = cr_instance_bind_material_set(s, instance, set); + return PyBool_FromLong(ret); +} + +static PyObject *py_cr_scene_set_background(PyObject *self, PyObject *args) { + (void)self; (void)args; + PyObject *s_ext; + PyObject *node_desc; + if (!PyArg_ParseTuple(args, "OO", &s_ext, &node_desc)) { + return NULL; + } + struct cr_scene *s = PyCapsule_GetPointer(s_ext, "cray.cr_scene"); + if (!PyCapsule_IsValid(node_desc, "cray.shader_node")) { + cr_scene_set_background(s, NULL); + Py_RETURN_NONE; + } + struct cr_shader_node *desc = PyCapsule_GetPointer(node_desc, "cray.shader_node"); + bool ret = cr_scene_set_background(s, desc); + return PyBool_FromLong(ret); +} + +static PyObject *py_cr_start_render_worker(PyObject *self, PyObject *args) { + (void)self; (void)args; + int port = 2222; + size_t thread_limit = 0; + if (!PyArg_ParseTuple(args, "|in", &port, &thread_limit)) { + return NULL; + } + Py_BEGIN_ALLOW_THREADS + cr_start_render_worker(port, thread_limit); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + +static PyObject *py_cr_send_shutdown_to_workers(PyObject *self, PyObject *args) { + (void)self; (void)args; + char *str = NULL; + if (!PyArg_ParseTuple(args, "s", &str)) { + return NULL; + } + Py_BEGIN_ALLOW_THREADS + cr_send_shutdown_to_workers(str); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + +static PyObject *py_cr_load_json(PyObject *self, PyObject *args) { + (void)self; + PyObject *r_ext; + char *path = NULL; + if (!PyArg_ParseTuple(args, "Os", &r_ext, &path)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + bool ret = cr_load_json(r, path); + return PyBool_FromLong(ret); +} + +static PyObject *py_log_level_set(PyObject *self, PyObject *args) { + (void)self; + enum cr_log_level level; + if (!PyArg_ParseTuple(args, "I", &level)) { + return NULL; + } + cr_log_level_set(level); + Py_RETURN_NONE; +} + +static PyObject *py_log_level_get(PyObject *self, PyObject *args) { + (void)self; + (void)args; + return PyLong_FromLong(cr_log_level_get()); +} + +static PyObject *py_debug_dump_state(PyObject *self, PyObject *args) { + (void)self; + PyObject *r_ext; + if (!PyArg_ParseTuple(args, "O", &r_ext)) { + return NULL; + } + struct cr_renderer *r = PyCapsule_GetPointer(r_ext, "cray.cr_renderer"); + cr_debug_dump_state(r); + Py_RETURN_NONE; +} +static PyMethodDef cray_methods[] = { + { "get_version", py_cr_get_version, METH_NOARGS, "" }, + { "get_git_hash", py_cr_get_git_hash, METH_NOARGS, "" }, + { "renderer_destroy", py_cr_destroy_renderer, METH_VARARGS, "" }, + { "new_renderer", py_cr_new_renderer, METH_NOARGS, "" }, + // { "destroy_renderer", py_cr_destroy_renderer, METH_VARARGS, "" }, + { "renderer_set_num_pref", py_cr_renderer_set_num_pref, METH_VARARGS, "" }, + { "renderer_set_str_pref", py_cr_renderer_set_str_pref, METH_VARARGS, "" }, + { "renderer_set_callback", py_cr_renderer_set_callback, METH_VARARGS, "" }, + { "renderer_stop", py_cr_renderer_stop, METH_VARARGS, "" }, + { "renderer_restart", py_cr_renderer_restart, METH_VARARGS, "" }, + { "renderer_toggle_pause", py_cr_renderer_toggle_pause, METH_VARARGS, "" }, + { "renderer_get_str_pref", py_cr_renderer_get_str_pref, METH_VARARGS, "" }, + { "renderer_get_num_pref", py_cr_renderer_get_num_pref, METH_VARARGS, "" }, + { "renderer_get_result", py_cr_renderer_get_result, METH_VARARGS, "" }, + { "renderer_render", py_cr_renderer_render, METH_VARARGS, "" }, + { "renderer_start_interactive", py_cr_renderer_start_interactive, METH_VARARGS, "" }, + // { "bitmap_free", py_cr_bitmap_free, METH_VARARGS, "" }, + { "renderer_scene_get", py_cr_renderer_scene_get, METH_VARARGS, "" }, + { "scene_totals", py_cr_scene_totals, METH_VARARGS, "" }, + { "scene_add_sphere", py_cr_scene_add_sphere, METH_VARARGS, "" }, + { "scene_vertex_buf_new", py_cr_scene_vertex_buf_new, METH_VARARGS, "" }, + { "mesh_bind_vertex_buf", py_cr_mesh_bind_vertex_buf, METH_VARARGS, "" }, + { "mesh_bind_faces", py_cr_mesh_bind_faces, METH_VARARGS, "" }, + { "scene_mesh_new", py_cr_scene_mesh_new, METH_VARARGS, "" }, + { "scene_get_mesh", py_cr_scene_get_mesh, METH_VARARGS, "" }, + { "camera_new", py_cr_camera_new, METH_VARARGS, "" }, + { "camera_set_num_pref", py_cr_camera_set_num_pref, METH_VARARGS, "" }, + { "camera_get_num_pref", py_cr_camera_get_num_pref, METH_VARARGS, "" }, + { "camera_update", py_cr_camera_update, METH_VARARGS, "" }, + { "scene_new_material_set", py_cr_scene_new_material_set, METH_VARARGS, "" }, + { "material_set_add", py_cr_material_set_add, METH_VARARGS, "" }, + { "instance_new", py_cr_instance_new, METH_VARARGS, "" }, + { "instance_set_transform", py_cr_instance_set_transform, METH_VARARGS, "" }, + { "instance_transform", py_cr_instance_transform, METH_VARARGS, "" }, + { "instance_bind_material_set", py_cr_instance_bind_material_set, METH_VARARGS, "" }, + { "scene_set_background", py_cr_scene_set_background, METH_VARARGS, "" }, + { "start_render_worker", py_cr_start_render_worker, METH_VARARGS, "" }, + { "send_shutdown_to_workers", py_cr_send_shutdown_to_workers, METH_VARARGS, "" }, + { "load_json", py_cr_load_json, METH_VARARGS, "" }, + { "log_level_set", py_log_level_set, METH_VARARGS, "" }, + { "log_level_get", py_log_level_get, METH_NOARGS, "" }, + { "debug_dump_state", py_debug_dump_state, METH_VARARGS, "" }, + { NULL, NULL, 0, NULL } +}; + +static struct PyModuleDef cray_wrap = { + PyModuleDef_HEAD_INIT, + "cray", + NULL, + -1, + cray_methods +}; + +PyMODINIT_FUNC PyInit_cray_wrap(void) { + PyObject *m = NULL; + + if (PyType_Ready(&CallbackInfoType) < 0) + return NULL; + + m = PyModule_Create(&cray_wrap); + if (!m) return NULL; + + Py_INCREF(&CallbackInfoType); + if (PyModule_AddObject(m, "CallbackInfo", (PyObject *)&CallbackInfoType) < 0) { + Py_DECREF(&CallbackInfoType); + Py_DECREF(m); + return NULL; + } + return m; +}