diff options
author | Sergey Sharybin <sergey> | 2021-12-07 20:50:48 +0300 |
---|---|---|
committer | Brecht Van Lommel <brecht@blender.org> | 2021-12-07 21:01:42 +0300 |
commit | 5e37f70307bdacedd0f7da65f8b385bc1426f21d (patch) | |
tree | dbf2d3085b28c5a674e18eb698fe0456836eec3c /intern/cycles/blender | |
parent | a92805bf24f52e1764a5f930bbe9e4e8fcc7e24d (diff) |
Fix T93350: Cycles renders shows black during rendering huge resolutions
The root of the issue is caused by Cycles ignoring OpenGL limitation on
the maximum resolution of textures: Cycles was allocating texture of the
final render resolution. It was exceeding limitation on certain GPUs and
driver.
The idea is simple: use multiple textures for the display, each of which
will fit into OpenGL limitations.
There is some code which allows the display driver to know when to start
the new tile. Also added some code to allow force graphics interop to be
re-created. The latter one ended up not used in the final version of the
patch, but it might be helpful for other drivers implementation.
The tile size is limited to 8K now as it is the safest size for textures
on many GPUs and OpenGL drivers.
Differential Revision: https://developer.blender.org/D13385
Diffstat (limited to 'intern/cycles/blender')
-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 |
3 files changed, 507 insertions, 282 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; |