diff --git a/bindings/blender_init.py b/bindings/blender_init.py index 3db6e0a7..a4eca55e 100644 --- a/bindings/blender_init.py +++ b/bindings/blender_init.py @@ -22,6 +22,9 @@ import numpy as np import time import datetime +import gpu +from gpu_extras.presets import draw_texture_2d +import threading from . import ( c_ray @@ -165,18 +168,29 @@ def on_status_update(renderer_cb_info, args): print("Stopping c-ray") cr_renderer.stop() +def status_update_interactive(renderer_cb_info, args): + tag_redraw, tag_update = args + tag_redraw() + class CrayRender(bpy.types.RenderEngine): bl_idname = "C_RAY" bl_label = "c-ray for Blender" bl_use_preview = True bl_use_shading_nodes_custom = False + cr_interactive_running = False cr_renderer = None + old_mtx = None def __init__(self): print("c-ray initialized") self.cr_scene = None + self.draw_data = None + self.cr_interactive_running = False def __del__(self): + if self.cr_interactive_running: + self.cr_renderer.stop() + self.cr_interactive_running = False if self.cr_renderer: self.cr_renderer.close() print("c-ray deleted") @@ -322,6 +336,70 @@ def render(self, depsgraph): bm = self.cr_renderer.get_result() self.display_bitmap(bm) + def view_draw(self, context, depsgraph): + print("view_draw_start") + mtx = context.region_data.view_matrix.inverted() + if not self.old_mtx or mtx != self.old_mtx: + print("self.old_mtx != mtx, tagging update") + self.tag_update() + self.old_mtx = mtx + dimensions = (context.region.width, context.region.height) + gpu.state.blend_set('ALPHA_PREMULT') + self.bind_display_space_shader(depsgraph.scene) + if not self.draw_data or self.draw_data.dimensions != dimensions: + bm = self.cr_renderer.get_result() + if not bm: + print("No bitmap yet") + return + self.draw_data = CrayDrawData(dimensions, bm) + + self.draw_data.draw() + self.unbind_display_space_shader() + gpu.state.blend_set('NONE') + + print("view_draw_end") + + def view_update(self, context, depsgraph): + print("view_update_start") + if not self.cr_renderer: + self.cr_renderer = c_ray.renderer() + self.cr_renderer.prefs.asset_path = "" + self.cr_renderer.prefs.blender_mode = True + self.cr_scene = self.cr_renderer.scene_get() + self.sync_scene(depsgraph) + self.cr_renderer.prefs.samples = depsgraph.scene.c_ray.samples + self.cr_renderer.prefs.threads = depsgraph.scene.c_ray.threads + self.cr_renderer.prefs.tile_x = depsgraph.scene.c_ray.tile_size + self.cr_renderer.prefs.tile_y = depsgraph.scene.c_ray.tile_size + self.cr_renderer.prefs.bounces = depsgraph.scene.c_ray.bounces + self.cr_renderer.prefs.node_list = depsgraph.scene.c_ray.node_list + print("depsgraph_len: {}".format(len(depsgraph.object_instances))) + self.cr_renderer.prefs.is_iterative = 1 + cr_cam = self.cr_scene.cameras['Camera'] + mtx = context.region_data.view_matrix.inverted() + euler = mtx.to_euler('XYZ') + loc = mtx.to_translation() + cr_cam.set_param(c_ray.cam_param.pose_x, loc[0]) + cr_cam.set_param(c_ray.cam_param.pose_y, loc[1]) + cr_cam.set_param(c_ray.cam_param.pose_z, loc[2]) + cr_cam.set_param(c_ray.cam_param.pose_roll, euler[0]) + cr_cam.set_param(c_ray.cam_param.pose_pitch, euler[1]) + cr_cam.set_param(c_ray.cam_param.pose_yaw, euler[2]) + + cr_cam.set_param(c_ray.cam_param.res_x, context.region.width) + cr_cam.set_param(c_ray.cam_param.res_y, context.region.height) + cr_cam.set_param(c_ray.cam_param.blender_coord, 1) + + if self.cr_interactive_running == True: + print("bg thread already running, sending restart") + self.cr_renderer.restart() + else: + print("Kicking off background renderer") + self.cr_renderer.callbacks.on_interactive_pass_finished = (status_update_interactive, (self.tag_redraw, self.tag_update)) + self.cr_renderer.start_interactive() + self.cr_interactive_running = True + print("view_update_end") + def display_bitmap(self, bm): # Get float array from libc-ray print("Grabbing float array from lib") @@ -351,6 +429,46 @@ def display_bitmap(self, bm): self.end_result(result) +class CrayDrawData: + texture = None + def __init__(self, dimensions, bitmap): + self.dimensions = dimensions + self.bitmap = bitmap + # width, height = dimensions + + # float_count = self.bitmap.width * self.bitmap.height * self.bitmap.stride + # pixels = array('f', self.bitmap.data.float_ptr[:float_count]) + # pixels = gpu.types.Buffer('FLOAT', float_count, pixels) + + # try: + # self.texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=pixels) + # except ValueError: + # print("Texture creation didn't work. width: {}, height: {}".format(width, height)) + + def __del__(self): + try: + del self.texture + except AttributeError: + print("No self.texture") + + def draw(self): + # FIXME: I have no idea how to create a GPUTexture that points to my raw float array on the C side. + # So for now, just do a really slow copy of the data on every redraw instead. + width, height = self.dimensions + + float_count = self.bitmap.width * self.bitmap.height * self.bitmap.stride + pixels = array('f', self.bitmap.data.float_ptr[:float_count]) + pixels = gpu.types.Buffer('FLOAT', float_count, pixels) + try: + texture = gpu.types.GPUTexture((width, height), format='RGBA32F', data=pixels) + except ValueError: + print("Texture creation didn't work. width: {}, height: {}".format(width, height)) + + if texture: + draw_texture_2d(texture, (0, 0), texture.width, texture.height) + # if self.texture: + # draw_texture_2d(self.texture, (0, 0), self.texture.width, self.texture.height) + def register(): from . import properties from . import ui diff --git a/bindings/c_ray.py b/bindings/c_ray.py index 7c2593a8..5a1348d5 100644 --- a/bindings/c_ray.py +++ b/bindings/c_ray.py @@ -65,6 +65,7 @@ class _cr_cb_type(IntEnum): 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): @@ -91,6 +92,13 @@ def _set_on_status_update(self, fn_and_userdata): _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 @@ -390,12 +398,18 @@ def close(self): def stop(self): _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): + _lib.renderer_start_interactive(self.obj_ptr) + def get_result(self): ret = _lib.renderer_get_result(self.obj_ptr) if not ret: diff --git a/bindings/cray_wrap.c b/bindings/cray_wrap.c index 302a6879..06a3a06d 100644 --- a/bindings/cray_wrap.c +++ b/bindings/cray_wrap.c @@ -98,7 +98,7 @@ static PyObject *py_cr_renderer_set_callback(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "OIO|O", &r_ext, &callback_type, &py_callback_fn, &py_user_data)) { return NULL; } - if (callback_type > cr_cb_status_update) { + if (callback_type > cr_cb_on_interactive_pass_finished) { PyErr_SetString(PyExc_ValueError, "Unknown callback type"); return NULL; } @@ -127,7 +127,21 @@ static PyObject *py_cr_renderer_stop(PyObject *self, PyObject *args) { 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"); + cr_renderer_restart_interactive(r); Py_RETURN_NONE; } @@ -200,6 +214,17 @@ static PyObject *py_cr_renderer_render(PyObject *self, PyObject *args) { 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; @@ -618,11 +643,13 @@ static PyMethodDef cray_methods[] = { { "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, "" }, diff --git a/include/c-ray/c-ray.h b/include/c-ray/c-ray.h index e6b361d3..52d594cf 100644 --- a/include/c-ray/c-ray.h +++ b/include/c-ray/c-ray.h @@ -102,7 +102,8 @@ enum cr_renderer_callback { cr_cb_on_start = 0, cr_cb_on_stop, cr_cb_status_update, - cr_cb_on_state_changed + cr_cb_on_state_changed, + cr_cb_on_interactive_pass_finished, }; CR_EXPORT bool cr_renderer_set_callback(struct cr_renderer *ext, @@ -113,6 +114,7 @@ CR_EXPORT bool cr_renderer_set_callback(struct cr_renderer *ext, CR_EXPORT bool cr_renderer_set_num_pref(struct cr_renderer *ext, enum cr_renderer_param p, uint64_t num); CR_EXPORT bool cr_renderer_set_str_pref(struct cr_renderer *ext, enum cr_renderer_param p, const char *str); CR_EXPORT void cr_renderer_stop(struct cr_renderer *ext); +CR_EXPORT void cr_renderer_restart_interactive(struct cr_renderer *ext); CR_EXPORT void cr_renderer_toggle_pause(struct cr_renderer *ext); CR_EXPORT const char *cr_renderer_get_str_pref(struct cr_renderer *ext, enum cr_renderer_param p); CR_EXPORT uint64_t cr_renderer_get_num_pref(struct cr_renderer *ext, enum cr_renderer_param p); @@ -136,6 +138,7 @@ struct cr_bitmap { }; CR_EXPORT void cr_renderer_render(struct cr_renderer *r); +CR_EXPORT void cr_renderer_start_interactive(struct cr_renderer *ext); CR_EXPORT struct cr_bitmap *cr_renderer_get_result(struct cr_renderer *r); // -- Scene -- diff --git a/src/lib/api/c-ray.c b/src/lib/api/c-ray.c index 6dae89fc..3af4992f 100644 --- a/src/lib/api/c-ray.c +++ b/src/lib/api/c-ray.c @@ -61,7 +61,7 @@ bool cr_renderer_set_callback(struct cr_renderer *ext, void (*callback_fn)(struct cr_renderer_cb_info *, void *), void *user_data) { if (!ext) return false; - if (t > cr_cb_on_state_changed) return false; + if (t > cr_cb_on_interactive_pass_finished) return false; struct renderer *r = (struct renderer *)ext; r->state.callbacks[t].fn = callback_fn; r->state.callbacks[t].user_data = user_data; @@ -172,6 +172,9 @@ void cr_renderer_stop(struct cr_renderer *ext) { if (!ext) return; struct renderer *r = (struct renderer *)ext; r->state.render_aborted = true; + while (!r->state.interactive_exit_done) { + timer_sleep_ms(10); + } } void cr_renderer_toggle_pause(struct cr_renderer *ext) { @@ -718,6 +721,49 @@ void cr_renderer_render(struct cr_renderer *ext) { renderer_render(r); } +void cr_renderer_start_interactive(struct cr_renderer *ext) { + if (!ext) return; + struct renderer *r = (struct renderer *)ext; + r->prefs.iterative = true; + if (!r->prefs.threads) { + return; + } + renderer_start_interactive(r); +} + +void cr_renderer_restart_interactive(struct cr_renderer *ext) { + if (!ext) return; + struct renderer *r = (struct renderer *)ext; + if (!r->prefs.iterative) { + logr(warning, "restart: Not iterative, bailing\n"); + return; + } + if (!r->state.workers.count) { + logr(warning, "restart: No workers, bailing\n"); + return; + } + if (!r->state.result_buf) { + logr(warning, "restart: No result buf, bailing\n"); + return; + } + if (!r->state.current_set) { + logr(warning, "restart: No tile set, bailing\n"); + return; + } + // sus + r->state.finishedPasses = 1; + mutex_lock(r->state.current_set->tile_mutex); + tex_clear(r->state.result_buf); + r->state.current_set->finished = 0; + for (size_t i = 0; i < r->prefs.threads; ++i) { + // FIXME: Use array for workers + // FIXME: What about network renderers? + r->state.workers.items[i].totalSamples = 0; + } + mutex_release(r->state.current_set->tile_mutex); + logr(info, "Renderer restarted\n"); +} + struct cr_bitmap *cr_renderer_get_result(struct cr_renderer *ext) { if (!ext) return NULL; struct renderer *r = (struct renderer *)ext; diff --git a/src/lib/datatypes/tile.c b/src/lib/datatypes/tile.c index 1ccfcb79..32100f20 100644 --- a/src/lib/datatypes/tile.c +++ b/src/lib/datatypes/tile.c @@ -51,10 +51,20 @@ struct render_tile *tile_next_interactive(struct renderer *r, struct tile_set *s tile->index = set->finished++; } else { r->state.finishedPasses++; + struct cr_renderer_cb_info cb_info = { 0 }; + struct callback cb = r->state.callbacks[cr_cb_on_interactive_pass_finished]; + if (cb.fn) cb.fn(&cb_info, cb.user_data); + logr(info, "finished passes: %zu\n", r->state.finishedPasses); set->finished = 0; goto again; } } + if (!tile) { + if (r->state.render_aborted) return NULL; + logr(info, "Sleeping 300ms, finishedPasses: %zu\n", r->state.finishedPasses); + timer_sleep_ms(300); + goto again; + } mutex_release(set->tile_mutex); return tile; } diff --git a/src/lib/renderer/renderer.c b/src/lib/renderer/renderer.c index cc941bd0..223d5890 100644 --- a/src/lib/renderer/renderer.c +++ b/src/lib/renderer/renderer.c @@ -107,6 +107,22 @@ void update_cb_info(struct renderer *r, struct tile_set *set, struct cr_renderer } +static void *thread_stub(void *arg) { + struct renderer *r = thread_user_data(arg); + renderer_render(r); + return NULL; +} + +// Nonblocking function to make python happy, just shove the normal loop in a +// background thread. +void renderer_start_interactive(struct renderer *r) { + static struct cr_thread main_loop = { + .thread_fn = thread_stub, + }; + main_loop.user_data = r; + thread_start(&main_loop); +} + // TODO: Clean this up, it's ugly. void renderer_render(struct renderer *r) { //Check for CTRL-C @@ -133,6 +149,7 @@ void renderer_render(struct renderer *r) { } struct tile_set set = tile_quantize(camera->width, camera->height, r->prefs.tileWidth, r->prefs.tileHeight, r->prefs.tileOrder); + r->state.current_set = &set; for (size_t i = 0; i < r->scene->shader_buffers.count; ++i) { if (!r->scene->shader_buffers.items[i].bsdfs.count) { @@ -269,6 +286,8 @@ void renderer_render(struct renderer *r) { r->state.rendering = false; timer_sleep_ms(r->state.workers.items[0].paused ? paused_msec : active_msec); } + + r->state.current_set = NULL; //Make sure render threads are terminated before continuing (This blocks) for (size_t w = 0; w < r->state.workers.count; ++w) { @@ -281,6 +300,8 @@ void renderer_render(struct renderer *r) { } if (info_tiles) free(info_tiles); tile_set_free(&set); + logr(info, "Renderer exiting\n"); + r->state.interactive_exit_done = true; } // An interactive render thread that progressively diff --git a/src/lib/renderer/renderer.h b/src/lib/renderer/renderer.h index 90241335..42d2c1f1 100644 --- a/src/lib/renderer/renderer.h +++ b/src/lib/renderer/renderer.h @@ -44,12 +44,15 @@ struct callback { struct state { size_t finishedPasses; // For interactive mode bool rendering; + // TODO: Rename to request_abort, and use an enum for actual state bool render_aborted; //SDL listens for X key pressed, which sets this + bool interactive_exit_done; struct worker_arr workers; struct render_client_arr clients; - struct callback callbacks[4]; + struct callback callbacks[5]; struct texture *result_buf; + struct tile_set *current_set; }; /// Preferences data (Set by user) @@ -82,6 +85,7 @@ struct renderer { struct renderer *renderer_new(void); void renderer_render(struct renderer *r); +void renderer_start_interactive(struct renderer *r); void renderer_destroy(struct renderer *r); struct prefs default_prefs(); // TODO: Remove