Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrecht Van Lommel <brecht@blender.org>2021-10-24 15:19:19 +0300
committerBrecht Van Lommel <brecht@blender.org>2021-10-26 16:37:04 +0300
commitfd25e883e2807a151f673b87c152a59701a0df80 (patch)
tree9441933f32ba2672ca71c58842342a9c525e123e /intern/cycles/blender/display_driver.cpp
parentd7d40745fa09061a3117bd3669c5a46bbf611eae (diff)
Cycles: remove prefix from source code file names
Remove prefix of filenames that is the same as the folder name. This used to help when #includes were using individual files, but now they are always relative to the cycles root directory and so the prefixes are redundant. For patches and branches, git merge and rebase should be able to detect the renames and move over code to the right file.
Diffstat (limited to 'intern/cycles/blender/display_driver.cpp')
-rw-r--r--intern/cycles/blender/display_driver.cpp771
1 files changed, 771 insertions, 0 deletions
diff --git a/intern/cycles/blender/display_driver.cpp b/intern/cycles/blender/display_driver.cpp
new file mode 100644
index 00000000000..d5f6d85251e
--- /dev/null
+++ b/intern/cycles/blender/display_driver.cpp
@@ -0,0 +1,771 @@
+/*
+ * Copyright 2021 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "blender/display_driver.h"
+
+#include "device/device.h"
+#include "util/log.h"
+#include "util/opengl.h"
+
+extern "C" {
+struct RenderEngine;
+
+bool RE_engine_has_render_context(struct RenderEngine *engine);
+void RE_engine_render_context_enable(struct RenderEngine *engine);
+void RE_engine_render_context_disable(struct RenderEngine *engine);
+
+bool DRW_opengl_context_release();
+void DRW_opengl_context_activate(bool drw_state);
+
+void *WM_opengl_context_create();
+void WM_opengl_context_activate(void *gl_context);
+void WM_opengl_context_dispose(void *gl_context);
+void WM_opengl_context_release(void *context);
+}
+
+CCL_NAMESPACE_BEGIN
+
+/* --------------------------------------------------------------------
+ * BlenderDisplayShader.
+ */
+
+unique_ptr<BlenderDisplayShader> BlenderDisplayShader::create(BL::RenderEngine &b_engine,
+ BL::Scene &b_scene)
+{
+ if (b_engine.support_display_space_shader(b_scene)) {
+ return make_unique<BlenderDisplaySpaceShader>(b_engine, b_scene);
+ }
+
+ return make_unique<BlenderFallbackDisplayShader>();
+}
+
+int BlenderDisplayShader::get_position_attrib_location()
+{
+ if (position_attribute_location_ == -1) {
+ const uint shader_program = get_shader_program();
+ position_attribute_location_ = glGetAttribLocation(shader_program, position_attribute_name);
+ }
+ return position_attribute_location_;
+}
+
+int BlenderDisplayShader::get_tex_coord_attrib_location()
+{
+ if (tex_coord_attribute_location_ == -1) {
+ const uint shader_program = get_shader_program();
+ tex_coord_attribute_location_ = glGetAttribLocation(shader_program, tex_coord_attribute_name);
+ }
+ return tex_coord_attribute_location_;
+}
+
+/* --------------------------------------------------------------------
+ * BlenderFallbackDisplayShader.
+ */
+
+/* TODO move shaders to standalone .glsl file. */
+static const char *FALLBACK_VERTEX_SHADER =
+ "#version 330\n"
+ "uniform vec2 fullscreen;\n"
+ "in vec2 texCoord;\n"
+ "in vec2 pos;\n"
+ "out vec2 texCoord_interp;\n"
+ "\n"
+ "vec2 normalize_coordinates()\n"
+ "{\n"
+ " return (vec2(2.0) * (pos / fullscreen)) - vec2(1.0);\n"
+ "}\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = vec4(normalize_coordinates(), 0.0, 1.0);\n"
+ " texCoord_interp = texCoord;\n"
+ "}\n\0";
+
+static const char *FALLBACK_FRAGMENT_SHADER =
+ "#version 330\n"
+ "uniform sampler2D image_texture;\n"
+ "in vec2 texCoord_interp;\n"
+ "out vec4 fragColor;\n"
+ "\n"
+ "void main()\n"
+ "{\n"
+ " fragColor = texture(image_texture, texCoord_interp);\n"
+ "}\n\0";
+
+static void shader_print_errors(const char *task, const char *log, const char *code)
+{
+ LOG(ERROR) << "Shader: " << task << " error:";
+ LOG(ERROR) << "===== shader string ====";
+
+ stringstream stream(code);
+ string partial;
+
+ int line = 1;
+ while (getline(stream, partial, '\n')) {
+ if (line < 10) {
+ LOG(ERROR) << " " << line << " " << partial;
+ }
+ else {
+ LOG(ERROR) << line << " " << partial;
+ }
+ line++;
+ }
+ LOG(ERROR) << log;
+}
+
+static int compile_fallback_shader(void)
+{
+ const struct Shader {
+ const char *source;
+ const GLenum type;
+ } shaders[2] = {{FALLBACK_VERTEX_SHADER, GL_VERTEX_SHADER},
+ {FALLBACK_FRAGMENT_SHADER, GL_FRAGMENT_SHADER}};
+
+ const GLuint program = glCreateProgram();
+
+ for (int i = 0; i < 2; i++) {
+ const GLuint shader = glCreateShader(shaders[i].type);
+
+ string source_str = shaders[i].source;
+ const char *c_str = source_str.c_str();
+
+ glShaderSource(shader, 1, &c_str, NULL);
+ glCompileShader(shader);
+
+ GLint compile_status;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
+
+ if (!compile_status) {
+ GLchar log[5000];
+ GLsizei length = 0;
+ glGetShaderInfoLog(shader, sizeof(log), &length, log);
+ shader_print_errors("compile", log, c_str);
+ return 0;
+ }
+
+ glAttachShader(program, shader);
+ }
+
+ /* Link output. */
+ glBindFragDataLocation(program, 0, "fragColor");
+
+ /* Link and error check. */
+ glLinkProgram(program);
+
+ /* TODO(sergey): Find a way to nicely de-duplicate the error checking. */
+ GLint link_status;
+ glGetProgramiv(program, GL_LINK_STATUS, &link_status);
+ if (!link_status) {
+ GLchar log[5000];
+ GLsizei length = 0;
+ /* TODO(sergey): Is it really program passed to glGetShaderInfoLog? */
+ glGetShaderInfoLog(program, sizeof(log), &length, log);
+ shader_print_errors("linking", log, FALLBACK_VERTEX_SHADER);
+ shader_print_errors("linking", log, FALLBACK_FRAGMENT_SHADER);
+ return 0;
+ }
+
+ return program;
+}
+
+void BlenderFallbackDisplayShader::bind(int width, int height)
+{
+ create_shader_if_needed();
+
+ if (!shader_program_) {
+ return;
+ }
+
+ glUseProgram(shader_program_);
+ glUniform1i(image_texture_location_, 0);
+ glUniform2f(fullscreen_location_, width, height);
+}
+
+void BlenderFallbackDisplayShader::unbind()
+{
+}
+
+uint BlenderFallbackDisplayShader::get_shader_program()
+{
+ return shader_program_;
+}
+
+void BlenderFallbackDisplayShader::create_shader_if_needed()
+{
+ if (shader_program_ || shader_compile_attempted_) {
+ return;
+ }
+
+ shader_compile_attempted_ = true;
+
+ shader_program_ = compile_fallback_shader();
+ if (!shader_program_) {
+ return;
+ }
+
+ glUseProgram(shader_program_);
+
+ image_texture_location_ = glGetUniformLocation(shader_program_, "image_texture");
+ if (image_texture_location_ < 0) {
+ LOG(ERROR) << "Shader doesn't contain the 'image_texture' uniform.";
+ destroy_shader();
+ return;
+ }
+
+ fullscreen_location_ = glGetUniformLocation(shader_program_, "fullscreen");
+ if (fullscreen_location_ < 0) {
+ LOG(ERROR) << "Shader doesn't contain the 'fullscreen' uniform.";
+ destroy_shader();
+ return;
+ }
+}
+
+void BlenderFallbackDisplayShader::destroy_shader()
+{
+ glDeleteProgram(shader_program_);
+ shader_program_ = 0;
+}
+
+/* --------------------------------------------------------------------
+ * BlenderDisplaySpaceShader.
+ */
+
+BlenderDisplaySpaceShader::BlenderDisplaySpaceShader(BL::RenderEngine &b_engine,
+ BL::Scene &b_scene)
+ : b_engine_(b_engine), b_scene_(b_scene)
+{
+ DCHECK(b_engine_.support_display_space_shader(b_scene_));
+}
+
+void BlenderDisplaySpaceShader::bind(int /*width*/, int /*height*/)
+{
+ b_engine_.bind_display_space_shader(b_scene_);
+}
+
+void BlenderDisplaySpaceShader::unbind()
+{
+ b_engine_.unbind_display_space_shader();
+}
+
+uint BlenderDisplaySpaceShader::get_shader_program()
+{
+ if (!shader_program_) {
+ glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<int *>(&shader_program_));
+ }
+
+ if (!shader_program_) {
+ LOG(ERROR) << "Error retrieving shader program for display space shader.";
+ }
+
+ return shader_program_;
+}
+
+/* --------------------------------------------------------------------
+ * BlenderDisplayDriver.
+ */
+
+BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene)
+ : b_engine_(b_engine), display_shader_(BlenderDisplayShader::create(b_engine, b_scene))
+{
+ /* Create context while on the main thread. */
+ gl_context_create();
+}
+
+BlenderDisplayDriver::~BlenderDisplayDriver()
+{
+ gl_resources_destroy();
+}
+
+/* --------------------------------------------------------------------
+ * Update procedure.
+ */
+
+bool BlenderDisplayDriver::update_begin(const Params &params,
+ int texture_width,
+ int texture_height)
+{
+ /* Note that it's the responsibility of BlenderDisplayDriver to ensure updating and drawing
+ * the texture does not happen at the same time. This is achieved indirectly.
+ *
+ * When enabling the OpenGL context, it uses an internal mutex lock DST.gl_context_lock.
+ * This same lock is also held when do_draw() is called, which together ensure mutual
+ * exclusion.
+ *
+ * This locking is not performed on the Cycles side, because that would cause lock inversion. */
+ if (!gl_context_enable()) {
+ return false;
+ }
+
+ if (gl_render_sync_) {
+ glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED);
+ }
+
+ if (!gl_texture_resources_ensure()) {
+ gl_context_disable();
+ return false;
+ }
+
+ /* Update texture dimensions if needed. */
+ if (texture_.width != texture_width || texture_.height != texture_height) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, 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;
+ 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.
+ *
+ * NOTE: Allocate the PBO for the the size which will fit the final render resolution (as in,
+ * at a resolution divider 1. This was we don't need to recreate graphics interoperability
+ * objects which are costly and which are tied to the specific underlying buffer size.
+ * The downside of this approach is that when graphics interoperability is not used we are
+ * 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 size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height;
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_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;
+ }
+
+ /* 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;
+
+ return true;
+}
+
+void BlenderDisplayDriver::update_end()
+{
+ gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ glFlush();
+
+ gl_context_disable();
+}
+
+/* --------------------------------------------------------------------
+ * Texture buffer mapping.
+ */
+
+half4 *BlenderDisplayDriver::map_texture_buffer()
+{
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id);
+
+ half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(
+ glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY));
+ if (!mapped_rgba_pixels) {
+ 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;
+}
+
+void BlenderDisplayDriver::unmap_texture_buffer()
+{
+ glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+}
+
+/* --------------------------------------------------------------------
+ * Graphics interoperability.
+ */
+
+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;
+
+ return interop_dst;
+}
+
+void BlenderDisplayDriver::graphics_interop_activate()
+{
+ gl_context_enable();
+}
+
+void BlenderDisplayDriver::graphics_interop_deactivate()
+{
+ gl_context_disable();
+}
+
+/* --------------------------------------------------------------------
+ * Drawing.
+ */
+
+void BlenderDisplayDriver::clear()
+{
+ texture_.need_clear = true;
+}
+
+void BlenderDisplayDriver::set_zoom(float zoom_x, float zoom_y)
+{
+ zoom_ = make_float2(zoom_x, zoom_y);
+}
+
+void BlenderDisplayDriver::draw(const Params &params)
+{
+ /* See do_update_begin() for why no locking is required here. */
+ const bool transparent = true; // TODO(sergey): Derive this from Film.
+
+ if (!gl_draw_resources_ensure()) {
+ return;
+ }
+
+ if (use_gl_context_) {
+ gl_context_mutex_.lock();
+ }
+
+ 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();
+ 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);
+ }
+
+ display_shader_->bind(params.full_size.x, params.full_size.y);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture_.gl_id);
+
+ /* Trick to keep sharp rendering without jagged edges on all GPUs.
+ *
+ * The idea here is to enforce driver to use linear interpolation when the image is not zoomed
+ * in.
+ * For the render result with a resolution divider in effect we always use nearest interpolation.
+ *
+ * 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) {
+ /* 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) {
+ 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_);
+
+ texture_update_if_needed();
+ vertex_buffer_update(params);
+
+ /* TODO(sergey): Does it make sense/possible to cache/reuse the VAO? */
+ GLuint vertex_array_object;
+ glGenVertexArrays(1, &vertex_array_object);
+ glBindVertexArray(vertex_array_object);
+
+ 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));
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glDeleteVertexArrays(1, &vertex_array_object);
+
+ display_shader_->unbind();
+
+ if (transparent) {
+ glDisable(GL_BLEND);
+ }
+
+ gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ glFlush();
+
+ if (use_gl_context_) {
+ gl_context_mutex_.unlock();
+ }
+}
+
+void BlenderDisplayDriver::gl_context_create()
+{
+ /* When rendering in viewport there is no render context available via engine.
+ * Check whether own context is to be created here.
+ *
+ * NOTE: If the `b_engine_`'s context is not available, we are expected to be on a main thread
+ * here. */
+ use_gl_context_ = !RE_engine_has_render_context(
+ reinterpret_cast<RenderEngine *>(b_engine_.ptr.data));
+
+ if (use_gl_context_) {
+ const bool drw_state = DRW_opengl_context_release();
+ gl_context_ = WM_opengl_context_create();
+ if (gl_context_) {
+ /* On Windows an old context is restored after creation, and subsequent release of context
+ * generates a Win32 error. Harmless for users, but annoying to have possible misleading
+ * error prints in the console. */
+#ifndef _WIN32
+ WM_opengl_context_release(gl_context_);
+#endif
+ }
+ else {
+ LOG(ERROR) << "Error creating OpenGL context.";
+ }
+
+ DRW_opengl_context_activate(drw_state);
+ }
+}
+
+bool BlenderDisplayDriver::gl_context_enable()
+{
+ if (use_gl_context_) {
+ if (!gl_context_) {
+ return false;
+ }
+ gl_context_mutex_.lock();
+ WM_opengl_context_activate(gl_context_);
+ return true;
+ }
+
+ RE_engine_render_context_enable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data));
+ return true;
+}
+
+void BlenderDisplayDriver::gl_context_disable()
+{
+ if (use_gl_context_) {
+ if (gl_context_) {
+ WM_opengl_context_release(gl_context_);
+ gl_context_mutex_.unlock();
+ }
+ return;
+ }
+
+ RE_engine_render_context_disable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data));
+}
+
+void BlenderDisplayDriver::gl_context_dispose()
+{
+ if (gl_context_) {
+ const bool drw_state = DRW_opengl_context_release();
+
+ WM_opengl_context_activate(gl_context_);
+ WM_opengl_context_dispose(gl_context_);
+
+ DRW_opengl_context_activate(drw_state);
+ }
+}
+
+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;
+ }
+
+ 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