1
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2018 Esteban Tovagliari, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28
29 // appleseed.python headers.
30 #include "gillocks.h"
31
32 // appleseed.renderer headers.
33 #include "renderer/api/frame.h"
34 #include "renderer/api/rendering.h"
35
36 // appleseed.foundation headers.
37 #include "foundation/image/canvasproperties.h"
38 #include "foundation/image/color.h"
39 #include "foundation/image/image.h"
40 #include "foundation/platform/python.h"
41 #include "foundation/utility/autoreleaseptr.h"
42
43 // OpenGL
44 #include <glad/glad.h>
45
46 // Standard headers.
47 #include <cassert>
48 #include <cstddef>
49 #include <vector>
50
51 namespace bpy = boost::python;
52 using namespace foundation;
53 using namespace renderer;
54
55 // Some GL headers do not define GL_RGBA32F.
56 // Instead of adding an OpenGL extension library like glew
57 // as a dependency, manually define it if needed.
58 #ifndef GL_RGBA32F
59 #define GL_RGBA32F 0x8814
60 #endif
61
62 namespace
63 {
64 class BlenderProgressiveTileCallback
65 : public ITileCallback
66 {
67 public:
BlenderProgressiveTileCallback(const bpy::object & request_redraw_callback)68 explicit BlenderProgressiveTileCallback(const bpy::object& request_redraw_callback)
69 : m_buffer_width(0)
70 , m_buffer_height(0)
71 , m_updated_buffer(false)
72 , m_texture_width(0)
73 , m_texture_height(0)
74 , m_request_redraw_callback(request_redraw_callback)
75 , m_updated_data_buffer(false)
76 , m_shader_program_id(0)
77 , m_vao_id(0)
78 , m_vertex_vbo_id(0)
79 , m_texture_vbo_id(0)
80 , m_ebo_id(0)
81 {
82 gladLoadGL();
83 }
84
~BlenderProgressiveTileCallback()85 ~BlenderProgressiveTileCallback() override
86 {
87 delete_texture();
88 delete_buffers();
89 }
90
release()91 void release() override
92 {
93 delete this;
94 }
95
on_tiled_frame_begin(const Frame *)96 void on_tiled_frame_begin(const Frame*) override
97 {
98 PyErr_SetString(PyExc_RuntimeError, "BlenderProgressiveTileCallback cannot be used for final renders");
99 bpy::throw_error_already_set();
100 }
101
on_tiled_frame_end(const Frame *)102 void on_tiled_frame_end(const Frame*) override
103 {
104 PyErr_SetString(PyExc_RuntimeError, "BlenderProgressiveTileCallback cannot be used for final renders");
105 bpy::throw_error_already_set();
106 }
107
on_tile_begin(const Frame *,const size_t,const size_t)108 void on_tile_begin(const Frame*, const size_t, const size_t) override
109 {
110 PyErr_SetString(PyExc_RuntimeError, "BlenderProgressiveTileCallback cannot be used for final renders");
111 bpy::throw_error_already_set();
112 }
113
on_tile_end(const Frame *,const size_t,const size_t)114 void on_tile_end(const Frame*, const size_t, const size_t) override
115 {
116 PyErr_SetString(PyExc_RuntimeError, "BlenderProgressiveTileCallback cannot be used for final renders");
117 bpy::throw_error_already_set();
118 }
119
on_progressive_frame_update(const Frame * frame)120 void on_progressive_frame_update(const Frame* frame) override
121 {
122 Image& image = frame->image();
123
124 // Realloc the buffer if the image size changed since the last time.
125 const CanvasProperties& props = image.properties();
126
127 if (props.m_canvas_width != m_buffer_width || props.m_canvas_height != m_buffer_height)
128 {
129 m_buffer_width = props.m_canvas_width;
130 m_buffer_height = props.m_canvas_height;
131 m_buffer.resize(m_buffer_width * m_buffer_height * 4);
132 }
133
134 // Copy the pixels to the buffer.
135 for (size_t ty = 0; ty < props.m_tile_count_y; ++ty)
136 {
137 for (size_t tx = 0; tx < props.m_tile_count_x; ++tx)
138 copy_tile(image.tile(tx, ty), props, tx, ty);
139 }
140
141 m_updated_buffer = true;
142
143 // Call the request redraw Python callback.
144 if (m_request_redraw_callback)
145 {
146 ScopedGILLock lock;
147
148 try
149 {
150 m_request_redraw_callback();
151 }
152 catch (...)
153 {
154 // Don't let Python exceptions propagate into C++.
155 }
156 }
157 }
158
draw_pixels()159 void draw_pixels()
160 {
161 if (m_texture_width != m_buffer_width || m_texture_height != m_buffer_height)
162 delete_texture();
163
164 if (m_texture_id == 0)
165 allocate_texture();
166
167 if (m_texture_id != 0)
168 {
169 if (m_updated_buffer)
170 {
171 glBindTexture(GL_TEXTURE_2D, m_texture_id);
172 glTexImage2D(
173 GL_TEXTURE_2D,
174 0,
175 GL_RGBA32F,
176 static_cast<GLsizei>(m_texture_width),
177 static_cast<GLsizei>(m_texture_height),
178 0,
179 GL_RGBA,
180 GL_FLOAT,
181 m_buffer.data());
182 glBindTexture(GL_TEXTURE_2D, 0);
183 m_updated_buffer = false;
184 }
185
186 if (!m_updated_data_buffer)
187 {
188
189 m_indices =
190 {
191 0, 1, 3,
192 1, 2, 3
193 };
194
195 m_texture_coords =
196 {
197 0.0f, 1.0f,
198 1.0f, 1.0f,
199 1.0f, 0.0f,
200 0.0f, 0.0f
201 };
202
203 m_vertex_coords =
204 {
205 -1, -1,
206 1, -1,
207 1, 1,
208 -1, 1
209 };
210
211 // Get shader program set by Blender.
212 glGetIntegerv(GL_CURRENT_PROGRAM, &m_shader_program_id);
213
214 // Blender 2.79b and below don't create a vertex shader.
215 // Create vertex shader if there isn't one.
216 GLint shader_count;
217 glGetProgramiv(m_shader_program_id, GL_ATTACHED_SHADERS, &shader_count);
218 if (shader_count < 2)
219 create_vertex_shader();
220
221 m_texcoord_location = glGetAttribLocation(m_shader_program_id, "texCoord");
222 m_position_location = glGetAttribLocation(m_shader_program_id, "pos");
223
224 m_image_texture_location = glGetUniformLocation(m_shader_program_id, "image_texture");
225 m_model_view_projection_location = glGetUniformLocation(m_shader_program_id, "ModelViewProjectionMatrix");
226
227 glGenVertexArrays(1, &m_vao_id);
228 glGenBuffers(1, &m_vertex_vbo_id);
229 glGenBuffers(1, &m_texture_vbo_id);
230 glGenBuffers(1, &m_ebo_id);
231
232 glBindVertexArray(m_vao_id);
233
234 // Bind index buffer.
235 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo_id);
236 glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(int), &m_indices[0], GL_STATIC_DRAW);
237
238 // Bind texture buffer.
239 glBindBuffer(GL_ARRAY_BUFFER, m_texture_vbo_id);
240 glBufferData(GL_ARRAY_BUFFER, m_texture_coords.size() * sizeof(float), &m_texture_coords[0], GL_STATIC_DRAW);
241 glEnableVertexAttribArray(m_texcoord_location);
242 glVertexAttribPointer(m_texcoord_location, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), reinterpret_cast<void*>(0));
243
244 // Bind vertex buffer.
245 glBindBuffer(GL_ARRAY_BUFFER, m_vertex_vbo_id);
246 glBufferData(GL_ARRAY_BUFFER, m_vertex_coords.size() * sizeof(float), &m_vertex_coords[0], GL_STATIC_DRAW);
247 glEnableVertexAttribArray(m_position_location);
248 glVertexAttribPointer(m_position_location, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), reinterpret_cast<void*>(0));
249
250 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
251 glBindBuffer(GL_ARRAY_BUFFER, 0);
252
253 m_updated_data_buffer = true;
254 }
255 glBindVertexArray(m_vao_id);
256
257 glActiveTexture(GL_TEXTURE0);
258 glBindTexture(GL_TEXTURE_2D, m_texture_id);
259
260 glUniform1i(m_image_texture_location, 0);
261 if (m_model_view_projection_location != -1)
262 glUniformMatrix4fv(m_model_view_projection_location, 1, GL_TRUE, &(Matrix4f::identity()[0]));
263
264 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo_id);
265 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
266
267 glBindVertexArray(0);
268 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
269 glBindTexture(GL_TEXTURE_2D, 0);
270 }
271 }
272
273 private:
274 std::vector<float> m_buffer;
275 size_t m_buffer_width;
276 size_t m_buffer_height;
277 bool m_updated_buffer;
278
279 GLuint m_texture_id;
280 size_t m_texture_width;
281 size_t m_texture_height;
282
283 bpy::object m_request_redraw_callback;
284
285 std::array<GLfloat, 8> m_vertex_coords;
286 std::array<GLfloat, 8> m_texture_coords;
287 std::array<GLuint, 6> m_indices;
288
289 GLint m_shader_program_id;
290 GLint m_texcoord_location;
291 GLint m_position_location;
292 GLint m_image_texture_location;
293 GLint m_model_view_projection_location;
294 GLuint m_vao_id;
295 GLuint m_vertex_vbo_id;
296 GLuint m_texture_vbo_id;
297 GLuint m_ebo_id;
298 bool m_updated_data_buffer;
299
delete_texture()300 void delete_texture()
301 {
302 if (m_texture_id != 0)
303 {
304 glDeleteTextures(1, &m_texture_id);
305
306 m_texture_id = 0;
307 m_texture_width = 0;
308 m_texture_height = 0;
309 }
310 }
311
delete_buffers()312 void delete_buffers()
313 {
314 if (m_updated_data_buffer)
315 {
316 glDeleteVertexArrays(1, &m_vao_id);
317 glDeleteBuffers(1, &m_vertex_vbo_id);
318 glDeleteBuffers(1, &m_texture_vbo_id);
319 glDeleteBuffers(1, &m_ebo_id);
320
321 m_updated_data_buffer = false;
322 }
323 }
324
create_vertex_shader()325 void create_vertex_shader()
326 {
327 // version 1.3 was used because gl_TexCoord is deprecated in newer versions.
328 const char* vertex_shader_source =
329 "#version 130\n"
330 "attribute vec2 texCoord;\n"
331 "attribute vec2 pos;\n"
332 "void main()\n"
333 "{\n"
334 " gl_Position = vec4(pos, 0.0, 1.0);\n"
335 " gl_TexCoord[0].st = texCoord\n;"
336 "}";
337
338 GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
339 glShaderSource(vertex_shader_id, 1, &vertex_shader_source, nullptr);
340 glCompileShader(vertex_shader_id);
341
342 glAttachShader(m_shader_program_id, vertex_shader_id);
343 glLinkProgram(m_shader_program_id);
344
345 glDeleteShader(vertex_shader_id);
346 }
347
allocate_texture()348 void allocate_texture()
349 {
350 assert(m_texture_id == 0);
351
352 // We didn't receive any pixel yet.
353 if (m_buffer_width == 0 || m_buffer_height == 0)
354 return;
355
356 glGenTextures(1, &m_texture_id);
357
358 m_texture_width = m_buffer_width;
359 m_texture_height = m_buffer_height;
360
361 glBindTexture(GL_TEXTURE_2D, m_texture_id);
362 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
363 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
364 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
365 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
366 glTexImage2D(
367 GL_TEXTURE_2D,
368 0,
369 GL_RGBA32F,
370 static_cast<GLsizei>(m_texture_width),
371 static_cast<GLsizei>(m_texture_height),
372 0,
373 GL_RGBA,
374 GL_FLOAT,
375 nullptr);
376 glBindTexture(GL_TEXTURE_2D, 0);
377 }
378
copy_tile(const Tile & tile,const CanvasProperties & props,const size_t tile_x,const size_t tile_y)379 void copy_tile(const Tile& tile, const CanvasProperties& props, const size_t tile_x, const size_t tile_y)
380 {
381 const size_t x0 = tile_x * props.m_tile_width;
382 const size_t y0 = tile_y * props.m_tile_height;
383
384 for (size_t y = 0, ye = tile.get_height(); y < ye; ++y)
385 {
386 const size_t offset = (((y0 + y) * props.m_canvas_width) + x0) * 4;
387
388 float* p = m_buffer.data() + offset;
389
390 for (size_t x = 0, xe = tile.get_width(); x < xe; ++x)
391 {
392 Color4f c;
393 tile.get_pixel(x, y, c);
394
395 *p++ = c.r;
396 *p++ = c.g;
397 *p++ = c.b;
398 *p++ = c.a;
399 }
400 }
401 }
402 };
403
create_blender_progressive_tile_callback(const bpy::object & request_redraw_callback)404 auto_release_ptr<BlenderProgressiveTileCallback> create_blender_progressive_tile_callback(const bpy::object& request_redraw_callback)
405 {
406 return auto_release_ptr<BlenderProgressiveTileCallback>(new BlenderProgressiveTileCallback(request_redraw_callback));
407 }
408 }
409
410 // Work around a regression in Visual Studio 2015 Update 3.
411 #if defined(_MSC_VER) && _MSC_VER == 1900
412 namespace boost
413 {
get_pointer(BlenderProgressiveTileCallback const volatile * p)414 template <> BlenderProgressiveTileCallback const volatile* get_pointer<BlenderProgressiveTileCallback const volatile>(BlenderProgressiveTileCallback const volatile* p) { return p; }
415 }
416 #endif
417
bind_blender_progressive_tile_callback()418 void bind_blender_progressive_tile_callback()
419 {
420 bpy::class_<BlenderProgressiveTileCallback, auto_release_ptr<BlenderProgressiveTileCallback>, bpy::bases<ITileCallback>, boost::noncopyable>("BlenderProgressiveTileCallback", bpy::no_init)
421 .def("__init__", bpy::make_constructor(create_blender_progressive_tile_callback))
422 .def("draw_pixels", &BlenderProgressiveTileCallback::draw_pixels);
423 }
424