Skip to content

Commit

Permalink
lib+blender: Implement a very buggy and slow, but functional interactive
Browse files Browse the repository at this point in the history
mode :^)

It's not very pretty, and I'm doing slow copies of the bitmap on every
redraw, and only changes to a single camera get updated, but it works!
Lots of room for improvement, I spent quite a bit of time slowly
chipping away at this over the last few weeks, while doing other
improvements.
  • Loading branch information
vkoskiv committed Jan 1, 2024
1 parent d1ea3f2 commit dd318b8
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 4 deletions.
118 changes: 118 additions & 0 deletions bindings/blender_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions bindings/c_ray.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
29 changes: 28 additions & 1 deletion bindings/cray_wrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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, "" },
Expand Down
5 changes: 4 additions & 1 deletion include/c-ray/c-ray.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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 --
Expand Down
48 changes: 47 additions & 1 deletion src/lib/api/c-ray.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/lib/datatypes/tile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit dd318b8

Please sign in to comment.