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