diff options
Diffstat (limited to 'intern')
-rw-r--r-- | intern/cycles/blender/addon/properties.py | 2 | ||||
-rw-r--r-- | intern/cycles/blender/display_driver.cpp | 720 | ||||
-rw-r--r-- | intern/cycles/blender/display_driver.h | 67 | ||||
-rw-r--r-- | intern/cycles/device/cuda/graphics_interop.cpp | 6 | ||||
-rw-r--r-- | intern/cycles/integrator/path_trace.cpp | 11 | ||||
-rw-r--r-- | intern/cycles/integrator/path_trace.h | 4 | ||||
-rw-r--r-- | intern/cycles/integrator/path_trace_display.cpp | 11 | ||||
-rw-r--r-- | intern/cycles/integrator/path_trace_display.h | 9 | ||||
-rw-r--r-- | intern/cycles/integrator/path_trace_work.cpp | 8 | ||||
-rw-r--r-- | intern/cycles/integrator/path_trace_work_gpu.cpp | 6 | ||||
-rw-r--r-- | intern/cycles/session/display_driver.h | 13 | ||||
-rw-r--r-- | intern/cycles/session/session.cpp | 5 | ||||
-rw-r--r-- | intern/cycles/session/tile.cpp | 6 | ||||
-rw-r--r-- | intern/cycles/session/tile.h | 6 |
14 files changed, 568 insertions, 306 deletions
diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 8569cb7d946..3dd6a02946b 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -799,7 +799,7 @@ class CyclesRenderSettings(bpy.types.PropertyGroup): name="Tile Size", default=2048, description="", - min=8, max=16384, + min=8, max=8192, ) # Various fine-tuning debug flags diff --git a/intern/cycles/blender/display_driver.cpp b/intern/cycles/blender/display_driver.cpp index abf421983b3..d902edc4695 100644 --- a/intern/cycles/blender/display_driver.cpp +++ b/intern/cycles/blender/display_driver.cpp @@ -273,11 +273,299 @@ uint BlenderDisplaySpaceShader::get_shader_program() } /* -------------------------------------------------------------------- + * DrawTile. + */ + +/* Higher level representation of a texture from the graphics library. */ +class GLTexture { + public: + /* Global counter for all allocated OpenGL textures used by instances of this class. */ + static inline std::atomic<int> num_used = 0; + + GLTexture() = default; + + ~GLTexture() + { + assert(gl_id == 0); + } + + GLTexture(const GLTexture &other) = delete; + GLTexture &operator=(GLTexture &other) = delete; + + GLTexture(GLTexture &&other) noexcept + : gl_id(other.gl_id), width(other.width), height(other.height) + { + other.reset(); + } + + GLTexture &operator=(GLTexture &&other) + { + if (this == &other) { + return *this; + } + + gl_id = other.gl_id; + width = other.width; + height = other.height; + + other.reset(); + + return *this; + } + + bool gl_resources_ensure() + { + if (gl_id) { + return true; + } + + /* Create texture. */ + glGenTextures(1, &gl_id); + if (!gl_id) { + LOG(ERROR) << "Error creating texture."; + return false; + } + + /* Configure the texture. */ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, gl_id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + /* Clamp to edge so that precision issues when zoomed out (which forces linear interpolation) + * does not cause unwanted repetition. */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + ++num_used; + + return true; + } + + void gl_resources_destroy() + { + if (!gl_id) { + return; + } + + glDeleteTextures(1, &gl_id); + + reset(); + + --num_used; + } + + /* OpenGL resource IDs of the texture. + * + * NOTE: Allocated on the render engine's context. */ + uint gl_id = 0; + + /* Dimensions of the texture in pixels. */ + int width = 0; + int height = 0; + + protected: + void reset() + { + gl_id = 0; + width = 0; + height = 0; + } +}; + +/* Higher level representation of a Pixel Buffer Object (PBO) from the graphics library. */ +class GLPixelBufferObject { + public: + /* Global counter for all allocated OpenGL PBOs used by instances of this class. */ + static inline std::atomic<int> num_used = 0; + + GLPixelBufferObject() = default; + + ~GLPixelBufferObject() + { + assert(gl_id == 0); + } + + GLPixelBufferObject(const GLPixelBufferObject &other) = delete; + GLPixelBufferObject &operator=(GLPixelBufferObject &other) = delete; + + GLPixelBufferObject(GLPixelBufferObject &&other) noexcept + : gl_id(other.gl_id), width(other.width), height(other.height) + { + other.reset(); + } + + GLPixelBufferObject &operator=(GLPixelBufferObject &&other) + { + if (this == &other) { + return *this; + } + + gl_id = other.gl_id; + width = other.width; + height = other.height; + + other.reset(); + + return *this; + } + + bool gl_resources_ensure() + { + if (gl_id) { + return true; + } + + glGenBuffers(1, &gl_id); + if (!gl_id) { + LOG(ERROR) << "Error creating texture pixel buffer object."; + return false; + } + + ++num_used; + + return true; + } + + void gl_resources_destroy() + { + if (!gl_id) { + return; + } + + glDeleteBuffers(1, &gl_id); + + reset(); + + --num_used; + } + + /* OpenGL resource IDs of the PBO. + * + * NOTE: Allocated on the render engine's context. */ + uint gl_id = 0; + + /* Dimensions of the PBO. */ + int width = 0; + int height = 0; + + protected: + void reset() + { + gl_id = 0; + width = 0; + height = 0; + } +}; + +class DrawTile { + public: + DrawTile() = default; + ~DrawTile() = default; + + DrawTile(const DrawTile &other) = delete; + DrawTile &operator=(const DrawTile &other) = delete; + + DrawTile(DrawTile &&other) noexcept = default; + + DrawTile &operator=(DrawTile &&other) = default; + + bool gl_resources_ensure() + { + if (!texture.gl_resources_ensure()) { + gl_resources_destroy(); + return false; + } + + if (!gl_vertex_buffer) { + glGenBuffers(1, &gl_vertex_buffer); + if (!gl_vertex_buffer) { + LOG(ERROR) << "Error allocating tile VBO."; + gl_resources_destroy(); + return false; + } + } + + return true; + } + + void gl_resources_destroy() + { + texture.gl_resources_destroy(); + + if (gl_vertex_buffer) { + glDeleteBuffers(1, &gl_vertex_buffer); + gl_vertex_buffer = 0; + } + } + + inline bool ready_to_draw() const + { + return texture.gl_id != 0; + } + + /* Texture which contains pixels of the tile. */ + GLTexture texture; + + /* Display parameters the texture of this tile has been updated for. */ + BlenderDisplayDriver::Params params; + + /* OpenGL resources needed for drawing. */ + uint gl_vertex_buffer = 0; +}; + +class DrawTileAndPBO { + public: + bool gl_resources_ensure() + { + if (!tile.gl_resources_ensure() || !buffer_object.gl_resources_ensure()) { + gl_resources_destroy(); + return false; + } + + return true; + } + + void gl_resources_destroy() + { + tile.gl_resources_destroy(); + buffer_object.gl_resources_destroy(); + } + + DrawTile tile; + GLPixelBufferObject buffer_object; +}; + +/* -------------------------------------------------------------------- * BlenderDisplayDriver. */ +struct BlenderDisplayDriver::Tiles { + /* Resources of a tile which is being currently rendered. */ + DrawTileAndPBO current_tile; + + /* All tiles which rendering is finished and which content will not be changed. */ + struct { + vector<DrawTile> tiles; + + void gl_resources_destroy_and_clear() + { + for (DrawTile &tile : tiles) { + tile.gl_resources_destroy(); + } + + tiles.clear(); + } + } finished_tiles; +}; + BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene) - : b_engine_(b_engine), display_shader_(BlenderDisplayShader::create(b_engine, b_scene)) + : b_engine_(b_engine), + display_shader_(BlenderDisplayShader::create(b_engine, b_scene)), + tiles_(make_unique<Tiles>()) { /* Create context while on the main thread. */ gl_context_create(); @@ -292,6 +580,21 @@ BlenderDisplayDriver::~BlenderDisplayDriver() * Update procedure. */ +void BlenderDisplayDriver::next_tile_begin() +{ + if (!tiles_->current_tile.tile.ready_to_draw()) { + LOG(ERROR) + << "Unexpectedly moving to the next tile without any data provided for current tile."; + return; + } + + /* Moving to the next tile without giving render data for the current tile is not an expected + * situation. */ + DCHECK(!need_clear_); + + tiles_->finished_tiles.tiles.emplace_back(std::move(tiles_->current_tile.tile)); +} + bool BlenderDisplayDriver::update_begin(const Params ¶ms, int texture_width, int texture_height) @@ -312,24 +615,33 @@ bool BlenderDisplayDriver::update_begin(const Params ¶ms, glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED); } - if (!gl_texture_resources_ensure()) { + DrawTile ¤t_tile = tiles_->current_tile.tile; + GLPixelBufferObject ¤t_tile_buffer_object = tiles_->current_tile.buffer_object; + + /* Clear storage of all finished tiles when display clear is requested. + * Do it when new tile data is provided to handle the display clear flag in a single place. + * It also makes the logic reliable from the whether drawing did happen or not point of view. */ + if (need_clear_) { + tiles_->finished_tiles.gl_resources_destroy_and_clear(); + need_clear_ = false; + } + + if (!tiles_->current_tile.gl_resources_ensure()) { + tiles_->current_tile.gl_resources_destroy(); gl_context_disable(); return false; } /* Update texture dimensions if needed. */ - if (texture_.width != texture_width || texture_.height != texture_height) { + if (current_tile.texture.width != texture_width || + current_tile.texture.height != texture_height) { glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture_.gl_id); + glBindTexture(GL_TEXTURE_2D, current_tile.texture.gl_id); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA16F, texture_width, texture_height, 0, GL_RGBA, GL_HALF_FLOAT, 0); - texture_.width = texture_width; - texture_.height = texture_height; + current_tile.texture.width = texture_width; + current_tile.texture.height = texture_height; glBindTexture(GL_TEXTURE_2D, 0); - - /* Texture did change, and no pixel storage was provided. Tag for an explicit zeroing out to - * avoid undefined content. */ - texture_.need_clear = true; } /* Update PBO dimensions if needed. @@ -341,29 +653,58 @@ bool BlenderDisplayDriver::update_begin(const Params ¶ms, * sending too much data to GPU when resolution divider is not 1. */ /* TODO(sergey): Investigate whether keeping the PBO exact size of the texture makes non-interop * mode faster. */ - const int buffer_width = params.full_size.x; - const int buffer_height = params.full_size.y; - if (texture_.buffer_width != buffer_width || texture_.buffer_height != buffer_height) { + const int buffer_width = params.size.x; + const int buffer_height = params.size.y; + if (current_tile_buffer_object.width != buffer_width || + current_tile_buffer_object.height != buffer_height) { const size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height; - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, current_tile_buffer_object.gl_id); glBufferData(GL_PIXEL_UNPACK_BUFFER, size_in_bytes, 0, GL_DYNAMIC_DRAW); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - texture_.buffer_width = buffer_width; - texture_.buffer_height = buffer_height; + current_tile_buffer_object.width = buffer_width; + current_tile_buffer_object.height = buffer_height; } - /* New content will be provided to the texture in one way or another, so mark this in a - * centralized place. */ - texture_.need_update = true; - - texture_.params = params; + /* Store an updated parameters of the current tile. + * In theory it is only needed once per update of the tile, but doing it on every update is + * the easiest and is not expensive. */ + tiles_->current_tile.tile.params = params; return true; } +static void update_tile_texture_pixels(const DrawTileAndPBO &tile) +{ + const GLTexture &texture = tile.tile.texture; + + DCHECK_NE(tile.buffer_object.gl_id, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture.gl_id); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, tile.buffer_object.gl_id); + + glTexSubImage2D( + GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, GL_RGBA, GL_HALF_FLOAT, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); +} + void BlenderDisplayDriver::update_end() { + /* Unpack the PBO into the texture as soon as the new content is provided. + * + * This allows to ensure that the unpacking happens while resources like graphics interop (which + * lifetime is outside of control of the display driver) are still valid, as well as allows to + * move the tile from being current to finished immediately after this call. + * + * One concern with this approach is that if the update happens more often than drawing then + * doing the unpack here occupies GPU transfer for no good reason. However, the render scheduler + * takes care of ensuring updates don't happen that often. In regular applications redraw will + * happen much more often than this update. */ + update_tile_texture_pixels(tiles_->current_tile); + gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); @@ -376,7 +717,11 @@ void BlenderDisplayDriver::update_end() half4 *BlenderDisplayDriver::map_texture_buffer() { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); + const uint pbo_gl_id = tiles_->current_tile.buffer_object.gl_id; + + DCHECK_NE(pbo_gl_id, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_gl_id); half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>( glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); @@ -384,15 +729,6 @@ half4 *BlenderDisplayDriver::map_texture_buffer() LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object."; } - if (texture_.need_clear) { - const int64_t texture_width = texture_.width; - const int64_t texture_height = texture_.height; - memset(reinterpret_cast<void *>(mapped_rgba_pixels), - 0, - texture_width * texture_height * sizeof(half4)); - texture_.need_clear = false; - } - return mapped_rgba_pixels; } @@ -411,12 +747,9 @@ BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get { GraphicsInterop interop_dst; - interop_dst.buffer_width = texture_.buffer_width; - interop_dst.buffer_height = texture_.buffer_height; - interop_dst.opengl_pbo_id = texture_.gl_pbo_id; - - interop_dst.need_clear = texture_.need_clear; - texture_.need_clear = false; + interop_dst.buffer_width = tiles_->current_tile.buffer_object.width; + interop_dst.buffer_height = tiles_->current_tile.buffer_object.height; + interop_dst.opengl_pbo_id = tiles_->current_tile.buffer_object.gl_id; return interop_dst; } @@ -437,7 +770,7 @@ void BlenderDisplayDriver::graphics_interop_deactivate() void BlenderDisplayDriver::clear() { - texture_.need_clear = true; + need_clear_ = true; } void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y) @@ -445,42 +778,78 @@ void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y) zoom_ = make_float2(zoom_x, zoom_y); } -void BlenderDisplayDriver::draw(const Params ¶ms) +/* Update vertex buffer with new coordinates of vertex positions and texture coordinates. + * This buffer is used to render texture in the viewport. + * + * NOTE: The buffer needs to be bound. */ +static void vertex_buffer_update(const DisplayDriver::Params ¶ms) { - /* See do_update_begin() for why no locking is required here. */ - const bool transparent = true; // TODO(sergey): Derive this from Film. + const int x = params.full_offset.x; + const int y = params.full_offset.y; + + const int width = params.size.x; + const int height = params.size.y; - if (!gl_draw_resources_ensure()) { + /* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be + * rendered. */ + glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), NULL, GL_STREAM_DRAW); + + float *vpointer = reinterpret_cast<float *>(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); + if (!vpointer) { return; } - if (use_gl_context_) { - gl_context_mutex_.lock(); - } + vpointer[0] = 0.0f; + vpointer[1] = 0.0f; + vpointer[2] = x; + vpointer[3] = y; - if (texture_.need_clear) { - /* Texture is requested to be cleared and was not yet cleared. - * - * Do early return which should be equivalent of drawing all-zero texture. - * Watch out for the lock though so that the clear happening during update is properly - * synchronized here. */ - gl_context_mutex_.unlock(); + vpointer[4] = 1.0f; + vpointer[5] = 0.0f; + vpointer[6] = x + width; + vpointer[7] = y; + + vpointer[8] = 1.0f; + vpointer[9] = 1.0f; + vpointer[10] = x + width; + vpointer[11] = y + height; + + vpointer[12] = 0.0f; + vpointer[13] = 1.0f; + vpointer[14] = x; + vpointer[15] = y + height; + + glUnmapBuffer(GL_ARRAY_BUFFER); +} + +static void draw_tile(const float2 &zoom, + const int texcoord_attribute, + const int position_attribute, + const DrawTile &draw_tile) +{ + if (!draw_tile.ready_to_draw()) { return; } - if (gl_upload_sync_) { - glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); - } + const GLTexture &texture = draw_tile.texture; - if (transparent) { - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - } + DCHECK_NE(texture.gl_id, 0); + DCHECK_NE(draw_tile.gl_vertex_buffer, 0); - display_shader_->bind(params.full_size.x, params.full_size.y); + glBindBuffer(GL_ARRAY_BUFFER, draw_tile.gl_vertex_buffer); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture_.gl_id); + /* Draw at the parameters for which the texture has been updated for. This allows to always draw + * texture during bordered-rendered camera view without flickering. The validness of the display + * parameters for a texture is guaranteed by the initial "clear" state which makes drawing to + * have an early output. + * + * Such approach can cause some extra "jelly" effect during panning, but it is not more jelly + * than overlay of selected objects. Also, it's possible to redraw texture at an intersection of + * the texture draw parameters and the latest updated draw parameters (although, complexity of + * doing it might not worth it. */ + vertex_buffer_update(draw_tile.params); + + glBindTexture(GL_TEXTURE_2D, texture.gl_id); /* Trick to keep sharp rendering without jagged edges on all GPUs. * @@ -490,53 +859,93 @@ void BlenderDisplayDriver::draw(const Params ¶ms) * * Use explicit MIN assignment to make sure the driver does not have an undefined behavior at * the zoom level 1. The MAG filter is always NEAREST. */ - const float zoomed_width = params.size.x * zoom_.x; - const float zoomed_height = params.size.y * zoom_.y; - if (texture_.width != params.size.x || texture_.height != params.size.y) { + const float zoomed_width = draw_tile.params.size.x * zoom.x; + const float zoomed_height = draw_tile.params.size.y * zoom.y; + if (texture.width != draw_tile.params.size.x || texture.height != draw_tile.params.size.y) { /* Resolution divider is different from 1, force nearest interpolation. */ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } - else if (zoomed_width - params.size.x > 0.5f || zoomed_height - params.size.y > 0.5f) { + else if (zoomed_width - draw_tile.params.size.x > 0.5f || + zoomed_height - draw_tile.params.size.y > 0.5f) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } - glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); + glVertexAttribPointer( + texcoord_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)0); + glVertexAttribPointer(position_attribute, + 2, + GL_FLOAT, + GL_FALSE, + 4 * sizeof(float), + (const GLvoid *)(sizeof(float) * 2)); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +} - texture_update_if_needed(); - vertex_buffer_update(params); +void BlenderDisplayDriver::draw(const Params ¶ms) +{ + /* See do_update_begin() for why no locking is required here. */ + const bool transparent = true; // TODO(sergey): Derive this from Film. + + if (use_gl_context_) { + gl_context_mutex_.lock(); + } - /* TODO(sergey): Does it make sense/possible to cache/reuse the VAO? */ + if (need_clear_) { + /* Texture is requested to be cleared and was not yet cleared. + * + * Do early return which should be equivalent of drawing all-zero texture. + * Watch out for the lock though so that the clear happening during update is properly + * synchronized here. */ + if (use_gl_context_) { + gl_context_mutex_.unlock(); + } + return; + } + + if (gl_upload_sync_) { + glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); + } + + if (transparent) { + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + + glActiveTexture(GL_TEXTURE0); + + /* NOTE: THe VAO is to be allocated on the drawing context as it is not shared across contects. + * Simplest is to allocate it on every redraw so that it is possible to destroy it from a + * correct context. */ GLuint vertex_array_object; glGenVertexArrays(1, &vertex_array_object); glBindVertexArray(vertex_array_object); + display_shader_->bind(params.full_size.x, params.full_size.y); + const int texcoord_attribute = display_shader_->get_tex_coord_attrib_location(); const int position_attribute = display_shader_->get_position_attrib_location(); glEnableVertexAttribArray(texcoord_attribute); glEnableVertexAttribArray(position_attribute); - glVertexAttribPointer( - texcoord_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)0); - glVertexAttribPointer(position_attribute, - 2, - GL_FLOAT, - GL_FALSE, - 4 * sizeof(float), - (const GLvoid *)(sizeof(float) * 2)); + draw_tile(zoom_, texcoord_attribute, position_attribute, tiles_->current_tile.tile); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + for (const DrawTile &tile : tiles_->finished_tiles.tiles) { + draw_tile(zoom_, texcoord_attribute, position_attribute, tile); + } + + display_shader_->unbind(); - glBindBuffer(GL_ARRAY_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); glDeleteVertexArrays(1, &vertex_array_object); - display_shader_->unbind(); - if (transparent) { glDisable(GL_BLEND); } @@ -544,6 +953,11 @@ void BlenderDisplayDriver::draw(const Params ¶ms) gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); + if (VLOG_IS_ON(5)) { + VLOG(5) << "Number of textures: " << GLTexture::num_used; + VLOG(5) << "Number of PBOs: " << GLPixelBufferObject::num_used; + } + if (use_gl_context_) { gl_context_mutex_.unlock(); } @@ -618,154 +1032,16 @@ void BlenderDisplayDriver::gl_context_dispose() } } -bool BlenderDisplayDriver::gl_draw_resources_ensure() -{ - if (!texture_.gl_id) { - /* If there is no texture allocated, there is nothing to draw. Inform the draw call that it can - * can not continue. Note that this is not an unrecoverable error, so once the texture is known - * we will come back here and create all the GPU resources needed for draw. */ - return false; - } - - if (gl_draw_resource_creation_attempted_) { - return gl_draw_resources_created_; - } - gl_draw_resource_creation_attempted_ = true; - - if (!vertex_buffer_) { - glGenBuffers(1, &vertex_buffer_); - if (!vertex_buffer_) { - LOG(ERROR) << "Error creating vertex buffer."; - return false; - } - } - - gl_draw_resources_created_ = true; - - return true; -} - void BlenderDisplayDriver::gl_resources_destroy() { gl_context_enable(); - if (vertex_buffer_ != 0) { - glDeleteBuffers(1, &vertex_buffer_); - } - - if (texture_.gl_pbo_id) { - glDeleteBuffers(1, &texture_.gl_pbo_id); - texture_.gl_pbo_id = 0; - } - - if (texture_.gl_id) { - glDeleteTextures(1, &texture_.gl_id); - texture_.gl_id = 0; - } + tiles_->current_tile.gl_resources_destroy(); + tiles_->finished_tiles.gl_resources_destroy_and_clear(); gl_context_disable(); gl_context_dispose(); } -bool BlenderDisplayDriver::gl_texture_resources_ensure() -{ - if (texture_.creation_attempted) { - return texture_.is_created; - } - texture_.creation_attempted = true; - - DCHECK(!texture_.gl_id); - DCHECK(!texture_.gl_pbo_id); - - /* Create texture. */ - glGenTextures(1, &texture_.gl_id); - if (!texture_.gl_id) { - LOG(ERROR) << "Error creating texture."; - return false; - } - - /* Configure the texture. */ - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture_.gl_id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); - - /* Create PBO for the texture. */ - glGenBuffers(1, &texture_.gl_pbo_id); - if (!texture_.gl_pbo_id) { - LOG(ERROR) << "Error creating texture pixel buffer object."; - return false; - } - - /* Creation finished with a success. */ - texture_.is_created = true; - - return true; -} - -void BlenderDisplayDriver::texture_update_if_needed() -{ - if (!texture_.need_update) { - return; - } - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); - glTexSubImage2D( - GL_TEXTURE_2D, 0, 0, 0, texture_.width, texture_.height, GL_RGBA, GL_HALF_FLOAT, 0); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - texture_.need_update = false; -} - -void BlenderDisplayDriver::vertex_buffer_update(const Params & /*params*/) -{ - /* Draw at the parameters for which the texture has been updated for. This allows to always draw - * texture during bordered-rendered camera view without flickering. The validness of the display - * parameters for a texture is guaranteed by the initial "clear" state which makes drawing to - * have an early output. - * - * Such approach can cause some extra "jelly" effect during panning, but it is not more jelly - * than overlay of selected objects. Also, it's possible to redraw texture at an intersection of - * the texture draw parameters and the latest updated draw parameters (although, complexity of - * doing it might not worth it. */ - const int x = texture_.params.full_offset.x; - const int y = texture_.params.full_offset.y; - - const int width = texture_.params.size.x; - const int height = texture_.params.size.y; - - /* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be - * rendered. */ - glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), NULL, GL_STREAM_DRAW); - - float *vpointer = reinterpret_cast<float *>(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); - if (!vpointer) { - return; - } - - vpointer[0] = 0.0f; - vpointer[1] = 0.0f; - vpointer[2] = x; - vpointer[3] = y; - - vpointer[4] = 1.0f; - vpointer[5] = 0.0f; - vpointer[6] = x + width; - vpointer[7] = y; - - vpointer[8] = 1.0f; - vpointer[9] = 1.0f; - vpointer[10] = x + width; - vpointer[11] = y + height; - - vpointer[12] = 0.0f; - vpointer[13] = 1.0f; - vpointer[14] = x; - vpointer[15] = y + height; - - glUnmapBuffer(GL_ARRAY_BUFFER); -} - CCL_NAMESPACE_END diff --git a/intern/cycles/blender/display_driver.h b/intern/cycles/blender/display_driver.h index 66cfc8cffcc..b9773f5eadf 100644 --- a/intern/cycles/blender/display_driver.h +++ b/intern/cycles/blender/display_driver.h @@ -26,6 +26,7 @@ #include "util/thread.h" #include "util/unique_ptr.h" +#include "util/vector.h" CCL_NAMESPACE_BEGIN @@ -112,6 +113,8 @@ class BlenderDisplayDriver : public DisplayDriver { void set_zoom(float zoom_x, float zoom_y); protected: + virtual void next_tile_begin() override; + virtual bool update_begin(const Params ¶ms, int texture_width, int texture_height) override; virtual void update_end() override; @@ -128,27 +131,9 @@ class BlenderDisplayDriver : public DisplayDriver { void gl_context_disable(); void gl_context_dispose(); - /* Make sure texture is allocated and its initial configuration is performed. */ - bool gl_texture_resources_ensure(); - - /* Ensure all runtime GPU resources needed for drawing are allocated. - * Returns true if all resources needed for drawing are available. */ - bool gl_draw_resources_ensure(); - /* Destroy all GPU resources which are being used by this object. */ void gl_resources_destroy(); - /* Update GPU texture dimensions and content if needed (new pixel data was provided). - * - * NOTE: The texture needs to be bound. */ - void texture_update_if_needed(); - - /* Update vertex buffer with new coordinates of vertex positions and texture coordinates. - * This buffer is used to render texture in the viewport. - * - * NOTE: The buffer needs to be bound. */ - void vertex_buffer_update(const Params ¶ms); - BL::RenderEngine b_engine_; /* OpenGL context which is used the render engine doesn't have its own. */ @@ -159,50 +144,14 @@ class BlenderDisplayDriver : public DisplayDriver { /* Mutex used to guard the `gl_context_`. */ thread_mutex gl_context_mutex_; - /* Texture which contains pixels of the render result. */ - struct { - /* Indicates whether texture creation was attempted and succeeded. - * Used to avoid multiple attempts of texture creation on GPU issues or GPU context - * misconfiguration. */ - bool creation_attempted = false; - bool is_created = false; - - /* OpenGL resource IDs of the texture itself and Pixel Buffer Object (PBO) used to write - * pixels to it. - * - * NOTE: Allocated on the engine's context. */ - uint gl_id = 0; - uint gl_pbo_id = 0; - - /* Is true when new data was written to the PBO, meaning, the texture might need to be resized - * and new data is to be uploaded to the GPU. */ - bool need_update = false; - - /* Content of the texture is to be filled with zeroes. */ - std::atomic<bool> need_clear = true; - - /* Dimensions of the texture in pixels. */ - int width = 0; - int height = 0; - - /* Dimensions of the underlying PBO. */ - int buffer_width = 0; - int buffer_height = 0; - - /* Display parameters the texture has been updated for. */ - Params params; - } texture_; + /* Content of the display is to be filled with zeroes. */ + std::atomic<bool> need_clear_ = true; unique_ptr<BlenderDisplayShader> display_shader_; - /* Special track of whether GPU resources were attempted to be created, to avoid attempts of - * their re-creation on failure on every redraw. */ - bool gl_draw_resource_creation_attempted_ = false; - bool gl_draw_resources_created_ = false; - - /* Vertex buffer which hold vertices of a triangle fan which is textures with the texture - * holding the render result. */ - uint vertex_buffer_ = 0; + /* Opaque storage for an internal state and data for tiles. */ + struct Tiles; + unique_ptr<Tiles> tiles_; void *gl_render_sync_ = nullptr; void *gl_upload_sync_ = nullptr; diff --git a/intern/cycles/device/cuda/graphics_interop.cpp b/intern/cycles/device/cuda/graphics_interop.cpp index 30efefd9b6b..c75d7957460 100644 --- a/intern/cycles/device/cuda/graphics_interop.cpp +++ b/intern/cycles/device/cuda/graphics_interop.cpp @@ -45,8 +45,10 @@ void CUDADeviceGraphicsInterop::set_display_interop( need_clear_ = display_interop.need_clear; - if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) { - return; + if (!display_interop.need_recreate) { + if (opengl_pbo_id_ == display_interop.opengl_pbo_id && buffer_area_ == new_buffer_area) { + return; + } } CUDAContextScope scope(device_); diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index bdc18b1c0a1..28a012c2506 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -115,7 +115,9 @@ bool PathTrace::ready_to_reset() return false; } -void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_tile_params) +void PathTrace::reset(const BufferParams &full_params, + const BufferParams &big_tile_params, + const bool reset_rendering) { if (big_tile_params_.modified(big_tile_params)) { big_tile_params_ = big_tile_params; @@ -128,7 +130,7 @@ void PathTrace::reset(const BufferParams &full_params, const BufferParams &big_t * It is requires to inform about reset whenever it happens, so that the redraw state tracking is * properly updated. */ if (display_) { - display_->reset(full_params); + display_->reset(big_tile_params, reset_rendering); } render_state_.has_denoised_result = false; @@ -622,9 +624,8 @@ void PathTrace::update_display(const RenderWork &render_work) if (display_) { VLOG(3) << "Perform copy to GPUDisplay work."; - const int resolution_divider = render_work.resolution_divider; - const int texture_width = max(1, full_params_.width / resolution_divider); - const int texture_height = max(1, full_params_.height / resolution_divider); + const int texture_width = render_state_.effective_big_tile_params.window_width; + const int texture_height = render_state_.effective_big_tile_params.window_height; if (!display_->update_begin(texture_width, texture_height)) { LOG(ERROR) << "Error beginning GPUDisplay update."; return; diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h index 9b079352a63..1ae48f82810 100644 --- a/intern/cycles/integrator/path_trace.h +++ b/intern/cycles/integrator/path_trace.h @@ -72,7 +72,9 @@ class PathTrace { * render result. */ bool ready_to_reset(); - void reset(const BufferParams &full_params, const BufferParams &big_tile_params); + void reset(const BufferParams &full_params, + const BufferParams &big_tile_params, + bool reset_rendering); void device_free(); diff --git a/intern/cycles/integrator/path_trace_display.cpp b/intern/cycles/integrator/path_trace_display.cpp index c1cade923b1..d6279f3298c 100644 --- a/intern/cycles/integrator/path_trace_display.cpp +++ b/intern/cycles/integrator/path_trace_display.cpp @@ -26,15 +26,20 @@ PathTraceDisplay::PathTraceDisplay(unique_ptr<DisplayDriver> driver) : driver_(m { } -void PathTraceDisplay::reset(const BufferParams &buffer_params) +void PathTraceDisplay::reset(const BufferParams &buffer_params, const bool reset_rendering) { thread_scoped_lock lock(mutex_); - params_.full_offset = make_int2(buffer_params.full_x, buffer_params.full_y); + params_.full_offset = make_int2(buffer_params.full_x + buffer_params.window_x, + buffer_params.full_y + buffer_params.window_y); params_.full_size = make_int2(buffer_params.full_width, buffer_params.full_height); - params_.size = make_int2(buffer_params.width, buffer_params.height); + params_.size = make_int2(buffer_params.window_width, buffer_params.window_height); texture_state_.is_outdated = true; + + if (!reset_rendering) { + driver_->next_tile_begin(); + } } void PathTraceDisplay::mark_texture_updated() diff --git a/intern/cycles/integrator/path_trace_display.h b/intern/cycles/integrator/path_trace_display.h index 47014f43afa..be301e55359 100644 --- a/intern/cycles/integrator/path_trace_display.h +++ b/intern/cycles/integrator/path_trace_display.h @@ -38,14 +38,17 @@ class BufferParams; class PathTraceDisplay { public: - PathTraceDisplay(unique_ptr<DisplayDriver> driver); + explicit PathTraceDisplay(unique_ptr<DisplayDriver> driver); virtual ~PathTraceDisplay() = default; /* Reset the display for the new state of render session. Is called whenever session is reset, * which happens on changes like viewport navigation or viewport dimension change. * - * This call will configure parameters for a changed buffer and reset the texture state. */ - void reset(const BufferParams &buffer_params); + * This call will configure parameters for a changed buffer and reset the texture state. + * + * When the `reset_rendering` a complete displat reset happens. When it is false reset happens + * for a new state of the buffer parameters which is assumed to correspond to the next tile. */ + void reset(const BufferParams &buffer_params, bool reset_rendering); /* -------------------------------------------------------------------- * Update procedure. diff --git a/intern/cycles/integrator/path_trace_work.cpp b/intern/cycles/integrator/path_trace_work.cpp index b0c40cfe15c..4ecc7d775ee 100644 --- a/intern/cycles/integrator/path_trace_work.cpp +++ b/intern/cycles/integrator/path_trace_work.cpp @@ -194,10 +194,10 @@ PassAccessor::Destination PathTraceWork::get_display_destination_template( PassAccessor::Destination destination(film_->get_display_pass()); const int2 display_texture_size = display->get_texture_size(); - const int texture_x = effective_buffer_params_.full_x - effective_full_params_.full_x + - effective_buffer_params_.window_x; - const int texture_y = effective_buffer_params_.full_y - effective_full_params_.full_y + - effective_buffer_params_.window_y; + const int texture_x = effective_buffer_params_.full_x - effective_big_tile_params_.full_x + + effective_buffer_params_.window_x - effective_big_tile_params_.window_x; + const int texture_y = effective_buffer_params_.full_y - effective_big_tile_params_.full_y + + effective_buffer_params_.window_y - effective_big_tile_params_.window_y; destination.offset = texture_y * display_texture_size.x + texture_x; destination.stride = display_texture_size.x; diff --git a/intern/cycles/integrator/path_trace_work_gpu.cpp b/intern/cycles/integrator/path_trace_work_gpu.cpp index e5062c6c47e..7a13447f2cf 100644 --- a/intern/cycles/integrator/path_trace_work_gpu.cpp +++ b/intern/cycles/integrator/path_trace_work_gpu.cpp @@ -875,8 +875,10 @@ void PathTraceWorkGPU::copy_to_display_naive(PathTraceDisplay *display, const int final_width = buffers_->params.window_width; const int final_height = buffers_->params.window_height; - const int texture_x = full_x - effective_full_params_.full_x + effective_buffer_params_.window_x; - const int texture_y = full_y - effective_full_params_.full_y + effective_buffer_params_.window_y; + const int texture_x = full_x - effective_big_tile_params_.full_x + + effective_buffer_params_.window_x - effective_big_tile_params_.window_x; + const int texture_y = full_y - effective_big_tile_params_.full_y + + effective_buffer_params_.window_y - effective_big_tile_params_.window_y; /* Re-allocate display memory if needed, and make sure the device pointer is allocated. * diff --git a/intern/cycles/session/display_driver.h b/intern/cycles/session/display_driver.h index 77f89326fd0..5544ceee83d 100644 --- a/intern/cycles/session/display_driver.h +++ b/intern/cycles/session/display_driver.h @@ -54,6 +54,8 @@ class DisplayDriver { } }; + virtual void next_tile_begin() = 0; + /* Update the render from the rendering thread. * * Cycles periodically updates the render to be displayed. For multithreaded updates with @@ -97,6 +99,17 @@ class DisplayDriver { /* Clear the entire buffer before doing partial write to it. */ bool need_clear = false; + + /* Enforce re-creation of the graphics interop object. + * + * When this field is true then the graphics interop will be re-created no matter what the + * rest of the configuration is. + * When this field is false the graphics interop will be re-created if the PBO or buffer size + * did change. + * + * This allows to ensure graphics interop is re-created when there is a possibility that an + * underlying PBO was re-allocated but did not change its ID. */ + bool need_recreate = false; }; virtual GraphicsInterop graphics_interop_get() diff --git a/intern/cycles/session/session.cpp b/intern/cycles/session/session.cpp index af5c6b3f1fd..4d8e3dfbfad 100644 --- a/intern/cycles/session/session.cpp +++ b/intern/cycles/session/session.cpp @@ -303,7 +303,7 @@ RenderWork Session::run_update_for_next_iteration() tile_params.update_offset_stride(); - path_trace_->reset(buffer_params_, tile_params); + path_trace_->reset(buffer_params_, tile_params, did_reset); } const int resolution = render_work.resolution_divider; @@ -384,7 +384,8 @@ int2 Session::get_effective_tile_size() const const int tile_size = tile_manager_.compute_render_tile_size(params.tile_size); const int64_t actual_tile_area = static_cast<int64_t>(tile_size) * tile_size; - if (actual_tile_area >= image_area) { + if (actual_tile_area >= image_area && image_width <= TileManager::MAX_TILE_SIZE && + image_height <= TileManager::MAX_TILE_SIZE) { return make_int2(image_width, image_height); } diff --git a/intern/cycles/session/tile.cpp b/intern/cycles/session/tile.cpp index 3b8482fa16f..afd1f334120 100644 --- a/intern/cycles/session/tile.cpp +++ b/intern/cycles/session/tile.cpp @@ -341,8 +341,10 @@ int TileManager::compute_render_tile_size(const int suggested_tile_size) const /* Must be a multiple of IMAGE_TILE_SIZE so that we can write render tiles into the image file * aligned on image tile boundaries. We can't set IMAGE_TILE_SIZE equal to the render tile size * because too big tile size leads to integer overflow inside OpenEXR. */ - return (suggested_tile_size <= IMAGE_TILE_SIZE) ? suggested_tile_size : - align_up(suggested_tile_size, IMAGE_TILE_SIZE); + const int computed_tile_size = (suggested_tile_size <= IMAGE_TILE_SIZE) ? + suggested_tile_size : + align_up(suggested_tile_size, IMAGE_TILE_SIZE); + return min(computed_tile_size, MAX_TILE_SIZE); } void TileManager::reset_scheduling(const BufferParams ¶ms, int2 tile_size) diff --git a/intern/cycles/session/tile.h b/intern/cycles/session/tile.h index eace148eb0a..7c8f7570d3e 100644 --- a/intern/cycles/session/tile.h +++ b/intern/cycles/session/tile.h @@ -122,6 +122,12 @@ class TileManager { /* Tile size in the image file. */ static const int IMAGE_TILE_SIZE = 128; + /* Maximum supported tile size. + * Needs to be safe from allocation on a GPU point of view: the display driver needs to be able + * to allocate texture with the side size of this value. + * Use conservative value which is safe for most of OpenGL drivers and GPUs. */ + static const int MAX_TILE_SIZE = 8192; + protected: /* Get tile configuration for its index. * The tile index must be within [0, state_.tile_state_). */ |