This topic covers how to write a display driver. Please be aware that writing a Display Driver involves a good understanding of the principles of multi-threaded programming.
If you're looking to redirect Clarisse render output, for example, to display an image in a custom widget, you'll need to create a Display Driver. A Display Driver is an abstract class that lets you grab the output of the rendering engine in real-time. In Clarisse, the Image View defines a Display Driver and connects it to the image chosen by the user. This is how the Image View in Clarisse is able to display the render progress.
There are no limitations to the number of display drivers connected to an image.
In Clarisse, images are evaluated by background worker threads. Each image has a predefined set of qualities, acting as multipliers to the image resolution and sampling quality when applicable. To render an image, the evaluation must request a specific quality. Qualities are processed one at the time and they can range from 1/16th of the resolution to full resolution. Please note the complete set of qualities is defined in the ModuleImage::Quality enumeration.
When display drivers are connected to an image, they are notified, during rendering, in the following order:
In this following example, we are going to create a floating window, a push button and a framebuffer display. When the user press the button, the display driver connects to the image currently selected in the application. It then request the image to be evaluated from the lower quality to the full resolution.
There are two classes that are important in this example: DisplayDriver and MyFrameBuffer. The DisplayDriver, here, does all the job and build an image made of buckets. Each rendered buckets are pushed in a list. These buckets are then displayed by MyFrameBuffer.
24 class DisplayDriver(ix.api.IOHelpersDisplayDriver):
26 Custom display driver that connects and listens to a Clarisse Image (ModuleImage).
27 It inherits from IOHelpersDisplayDriver which is an abstract display driver helper class.
30 def __init__(self, framebuffer):
31 ix.api.IOHelpersDisplayDriver.__init__(self)
32 self.framebuffer = framebuffer
33 self.buckets = ix.api.GMathVec4iVector()
34 self.rendered_regions = ix.api.GMathVec4iVector()
35 self.progress_image_w = 0
36 self.progress_image_h = 0
39 self.channels = ix.api.CoreStringVector()
40 self.channels.add(
'r')
41 self.channels.add('g')
42 self.channels.add(
'b')
45 def on_init_render(self, init_data):
46 """Called by the rendering thread when it starts to render the image."""
48 self.framebuffer.lock()
51 self.rendered_regions.clear()
54 self.framebuffer.image =
None
55 canvas = init_data.progress_image.get_canvas()
56 self.progress_image_w = canvas.get_width()
57 self.progress_image_h = canvas.get_height()
59 self.framebuffer.unlock()
61 self.framebuffer.must_draw_final_image =
False
62 self.framebuffer.draw_final_image(init_data.quality,
False)
63 self.framebuffer.redraw()
65 def on_end_render(self):
66 """Called by the rendering thread when the image is completed."""
68 self.framebuffer.lock()
70 self.rendered_regions.clear()
72 self.framebuffer.unlock()
74 def on_highlight_region(self, regions):
75 """Called by the rendering thread when starting to render new regions in the image."""
77 self.framebuffer.lock()
78 self.buckets = ix.api.GMathVec4iVector()
79 self.buckets.copy_from(regions)
80 self.framebuffer.unlock()
81 self.framebuffer.redraw()
83 def on_image_level_update(self, quality, image):
84 """Called by the rendering thread when the connected image completes a render quality."""
86 self.framebuffer.lock()
88 self.framebuffer.image =
None
89 self.framebuffer.must_draw_final_image =
False
90 self.progress_image_w = image.get_canvas().get_width()
91 self.progress_image_h = image.get_canvas().get_height()
92 self.framebuffer.draw_final_image(quality,
True)
94 self.rendered_regions.clear()
97 self.framebuffer.unlock()
98 self.framebuffer.redraw()
100 def on_draw_region(self, progress_image, regions):
101 """Called by the rendering thread when new regions are available to display."""
103 self.framebuffer.lock()
106 self.rendered_regions.append(regions)
107 canvas = progress_image.get_canvas()
108 bitmap = ix.api.ImageHelperBitmap()
109 for i
in range(regions.get_count()):
112 ix.api.ImageHelper.create_bitmap(bitmap, progress_image, r[0], r[1], r[2], r[3], self.channels)
114 tile = ix.api.GuiImage(bitmap.get_buffer(), bitmap.get_width(), bitmap.get_height(), self.channels.get_count())
116 self.tiles.append(tile)
118 self.framebuffer.unlock()
119 self.framebuffer.redraw()
122 class MyFrameBuffer(ix.api.GuiWidget):
124 Widget displaying the image.
127 def __init__(self, parent, x, y, w, h):
128 ix.api.GuiWidget.__init__(self, parent, x, y, w, h)
129 self.driver = DisplayDriver(self)
130 self.buckets = ix.api.GMathVec4iVector()
131 self.buckets_lock = threading.RLock()
132 self.must_draw_final_image =
False
136 self.buckets_lock.acquire()
139 self.buckets_lock.release()
142 if not self.driver.is_connected():
145 if not self.must_draw_final_image:
146 regions = ix.api.GMathVec4iVector()
147 rendered_regions = ix.api.GMathVec4iVector()
148 dc.draw_rectf(self.get_x(), self.get_y(), self.get_width(), self.get_height(), 0,0,0)
152 regions = self.driver.buckets
153 rendered_regions = self.driver.rendered_regions
156 if regions.get_count() != 0:
157 for i
in range(regions.get_count()):
159 dc.draw_rect(r[0] + self.get_x(), r[1] + self.get_y(), r[2], r[3], 255, 255, 255)
162 tiles = self.driver.tiles
163 if rendered_regions.get_count() != 0:
166 r = rendered_regions[i]
167 tile.draw(r[0] + self.get_x(), r[1] + self.get_y())
169 dc.draw_rect(self.get_x(), self.get_y(), self.driver.progress_image_w, self.driver.progress_image_h, 255,0,0)
172 dc.draw_rectf(self.get_x(), self.get_y(), self.get_width(), self.get_height(), 0,0,0)
173 if self.image !=
None:
174 self.image.draw(self.get_x(), self.get_y())
175 dc.draw_rect(self.get_x(), self.get_y(), self.image.get_width(), self.image.get_height(), 255, 255, 255)
177 def draw_final_image(self, quality, flag):
181 if flag != self.must_draw_final_image:
182 self.must_draw_final_image = flag
188 bitmap = ix.api.ImageHelperBitmap()
189 module = self.driver.get_image()
190 img = module.get_image(quality)
191 ix.api.ImageHelper.create_bitmap(bitmap, img, 0, 0, img.get_canvas().get_width(), img.get_canvas().get_height(), self.driver.channels)
192 self.image = ix.api.GuiImage(bitmap.get_buffer(), bitmap.get_width(), bitmap.get_height(), self.driver.channels.get_count())
198 class MyButton(ix.api.GuiPushButton):
200 Custom button that connects the display driver to the selected image and requests its render and display.
203 def __init__(self, parent, x, y, w, h):
204 ix.api.GuiPushButton.__init__(self, parent, x, y, w, h,
"Connect to Selected Image")
205 self.connect(self,
'EVT_ID_PUSH_BUTTON_CLICK', self.on_click)
207 def on_click(self, sender, evtid):
208 if ix.selection.is_empty():
209 ix.log_info(
"Select an Image item.")
212 item = ix.selection[0]
213 if item
is not None and item.is_kindof(
"Image") ==
False:
214 ix.log_info(
"The selected item is not an Image")
217 framebuffer = self.get_parent().framebuffer
219 if framebuffer.driver.is_connected():
220 framebuffer.driver.disconnect()
224 framebuffer.buckets.clear()
225 framebuffer.driver.rendered_regions.clear()
226 framebuffer.tiles = []
230 if framebuffer.driver.connect(item.get_module()) ==
False:
231 ix.log_warning(
"Failed to connect to Image {}".format(item.get_full_name()))
234 self.get_parent().set_title(
"Python Render Window: " + item.get_full_name())
237 if item.get_module().is_image_dirty(ix.api.ModuleImageQuality.QUALITY_FULL):
239 framebuffer.draw_final_image(ix.api.ModuleImageQuality.QUALITY_FULL,
False)
243 framebuffer.driver.get_image().compute_image(ix.api.ModuleImageQuality.QUALITY_ONE_SIXTEENTH, ix.api.ModuleImageQuality.QUALITY_FULL)
246 framebuffer.draw_final_image(ix.api.ModuleImageQuality.QUALITY_FULL,
True)
248 class MyRenderWindow(ix.api.GuiWindow):
250 Main window showing the button and the image.
253 def __init__(self, application, x, y, w, h):
254 ix.api.GuiWindow.__init__(self, application, x, y, w, h,
"Python Render Window: (none)")
255 self.button = MyButton(self, 0, 0, 256, 22)
256 self.framebuffer = MyFrameBuffer(self, 0, 22, w, h - 22)
257 self.framebuffer.set_constraints(ix.api.GuiWidget.CONSTRAINT_LEFT, ix.api.GuiWidget.CONSTRAINT_TOP, ix.api.GuiWidget.CONSTRAINT_RIGHT, ix.api.GuiWidget.CONSTRAINT_BOTTOM)
261 self.framebuffer.driver.disconnect()
266 self.framebuffer.driver =
None
270 render_window = MyRenderWindow(ix.application, 0, 0, 640, 480)
274 while render_window.is_shown():
275 ix.application.check_for_events(
True)