From a754e35198d852ea34e2b82cd2b126538e6f5a3b Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 14 Sep 2021 15:37:47 +0200 Subject: Cycles: refactor API for GPU display * Split GPUDisplay into two classes. PathTraceDisplay to implement the Cycles side, and DisplayDriver to implement the host application side. The DisplayDriver is now a fully abstract base class, embedded in the PathTraceDisplay. * Move copy_pixels_to_texture implementation out of the host side into the Cycles side, since it can be implemented in terms of the texture buffer mapping. * Move definition of DeviceGraphicsInteropDestination into display driver header, so that we do not need to expose private device headers in the public API. * Add more detailed comments about how the DisplayDriver should be implemented. The "driver" terminology might not be obvious, but is also used in other renderers. Differential Revision: https://developer.blender.org/D12626 --- intern/cycles/blender/CMakeLists.txt | 4 +- intern/cycles/blender/blender_display_driver.cpp | 754 +++++++++++++++++++++ intern/cycles/blender/blender_display_driver.h | 210 ++++++ intern/cycles/blender/blender_gpu_display.cpp | 791 ----------------------- intern/cycles/blender/blender_gpu_display.h | 215 ------ intern/cycles/blender/blender_session.cpp | 15 +- intern/cycles/blender/blender_session.h | 6 +- 7 files changed, 977 insertions(+), 1018 deletions(-) create mode 100644 intern/cycles/blender/blender_display_driver.cpp create mode 100644 intern/cycles/blender/blender_display_driver.h delete mode 100644 intern/cycles/blender/blender_gpu_display.cpp delete mode 100644 intern/cycles/blender/blender_gpu_display.h (limited to 'intern/cycles/blender') diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt index 64d226cb9ec..2660eee017b 100644 --- a/intern/cycles/blender/CMakeLists.txt +++ b/intern/cycles/blender/CMakeLists.txt @@ -31,9 +31,9 @@ set(INC_SYS set(SRC blender_camera.cpp blender_device.cpp + blender_display_driver.cpp blender_image.cpp blender_geometry.cpp - blender_gpu_display.cpp blender_light.cpp blender_mesh.cpp blender_object.cpp @@ -51,7 +51,7 @@ set(SRC CCL_api.h blender_device.h - blender_gpu_display.h + blender_display_driver.h blender_id_map.h blender_image.h blender_object_cull.h diff --git a/intern/cycles/blender/blender_display_driver.cpp b/intern/cycles/blender/blender_display_driver.cpp new file mode 100644 index 00000000000..5267f41eef7 --- /dev/null +++ b/intern/cycles/blender/blender_display_driver.cpp @@ -0,0 +1,754 @@ +/* + * 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/blender_display_driver.h" + +#include "device/device.h" +#include "util/util_logging.h" +#include "util/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::create(BL::RenderEngine &b_engine, + BL::Scene &b_scene) +{ + if (b_engine.support_display_space_shader(b_scene)) { + return make_unique(b_engine, b_scene); + } + + return make_unique(); +} + +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(&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 ¶ms, + 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; + + 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( + 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(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 ¶ms) +{ + /* 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. + * Watchout 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(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(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(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 ¶ms) +{ + /* 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(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); + if (!vpointer) { + return; + } + + vpointer[0] = 0.0f; + vpointer[1] = 0.0f; + vpointer[2] = params.full_offset.x; + vpointer[3] = params.full_offset.y; + + vpointer[4] = 1.0f; + vpointer[5] = 0.0f; + vpointer[6] = (float)params.size.x + params.full_offset.x; + vpointer[7] = params.full_offset.y; + + vpointer[8] = 1.0f; + vpointer[9] = 1.0f; + vpointer[10] = (float)params.size.x + params.full_offset.x; + vpointer[11] = (float)params.size.y + params.full_offset.y; + + vpointer[12] = 0.0f; + vpointer[13] = 1.0f; + vpointer[14] = params.full_offset.x; + vpointer[15] = (float)params.size.y + params.full_offset.y; + + glUnmapBuffer(GL_ARRAY_BUFFER); +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_display_driver.h b/intern/cycles/blender/blender_display_driver.h new file mode 100644 index 00000000000..558997c6b4f --- /dev/null +++ b/intern/cycles/blender/blender_display_driver.h @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "MEM_guardedalloc.h" + +#include "RNA_blender_cpp.h" + +#include "render/display_driver.h" + +#include "util/util_thread.h" +#include "util/util_unique_ptr.h" + +CCL_NAMESPACE_BEGIN + +/* Base class of shader used for display driver rendering. */ +class BlenderDisplayShader { + public: + static constexpr const char *position_attribute_name = "pos"; + static constexpr const char *tex_coord_attribute_name = "texCoord"; + + /* Create shader implementation suitable for the given render engine and scene configuration. */ + static unique_ptr create(BL::RenderEngine &b_engine, BL::Scene &b_scene); + + BlenderDisplayShader() = default; + virtual ~BlenderDisplayShader() = default; + + virtual void bind(int width, int height) = 0; + virtual void unbind() = 0; + + /* Get attribute location for position and texture coordinate respectively. + * NOTE: The shader needs to be bound to have access to those. */ + virtual int get_position_attrib_location(); + virtual int get_tex_coord_attrib_location(); + + protected: + /* Get program of this display shader. + * NOTE: The shader needs to be bound to have access to this. */ + virtual uint get_shader_program() = 0; + + /* Cached values of various OpenGL resources. */ + int position_attribute_location_ = -1; + int tex_coord_attribute_location_ = -1; +}; + +/* Implementation of display rendering shader used in the case when render engine does not support + * display space shader. */ +class BlenderFallbackDisplayShader : public BlenderDisplayShader { + public: + virtual void bind(int width, int height) override; + virtual void unbind() override; + + protected: + virtual uint get_shader_program() override; + + void create_shader_if_needed(); + void destroy_shader(); + + uint shader_program_ = 0; + int image_texture_location_ = -1; + int fullscreen_location_ = -1; + + /* Shader compilation attempted. Which means, that if the shader program is 0 then compilation or + * linking has failed. Do not attempt to re-compile the shader. */ + bool shader_compile_attempted_ = false; +}; + +class BlenderDisplaySpaceShader : public BlenderDisplayShader { + public: + BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, BL::Scene &b_scene); + + virtual void bind(int width, int height) override; + virtual void unbind() override; + + protected: + virtual uint get_shader_program() override; + + BL::RenderEngine b_engine_; + BL::Scene &b_scene_; + + /* Cached values of various OpenGL resources. */ + uint shader_program_ = 0; +}; + +/* Display driver implementation which is specific for Blender viewport integration. */ +class BlenderDisplayDriver : public DisplayDriver { + public: + BlenderDisplayDriver(BL::RenderEngine &b_engine, BL::Scene &b_scene); + ~BlenderDisplayDriver(); + + virtual void graphics_interop_activate() override; + virtual void graphics_interop_deactivate() override; + + virtual void clear() override; + + void set_zoom(float zoom_x, float zoom_y); + + protected: + virtual bool update_begin(const Params ¶ms, int texture_width, int texture_height) override; + virtual void update_end() override; + + virtual half4 *map_texture_buffer() override; + virtual void unmap_texture_buffer() override; + + virtual GraphicsInterop graphics_interop_get() override; + + virtual void draw(const Params ¶ms) override; + + /* Helper function which allocates new GPU context. */ + void gl_context_create(); + bool gl_context_enable(); + 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. */ + void *gl_context_ = nullptr; + /* The when Blender RenderEngine side context is not available and the DisplayDriver is to create + * its own context. */ + bool use_gl_context_ = false; + /* 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 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; + } texture_; + + unique_ptr 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; + + void *gl_render_sync_ = nullptr; + void *gl_upload_sync_ = nullptr; + + float2 zoom_ = make_float2(1.0f, 1.0f); +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_gpu_display.cpp b/intern/cycles/blender/blender_gpu_display.cpp deleted file mode 100644 index 5a4567deac3..00000000000 --- a/intern/cycles/blender/blender_gpu_display.cpp +++ /dev/null @@ -1,791 +0,0 @@ -/* - * 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/blender_gpu_display.h" - -#include "device/device.h" -#include "util/util_logging.h" -#include "util/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::create(BL::RenderEngine &b_engine, - BL::Scene &b_scene) -{ - if (b_engine.support_display_space_shader(b_scene)) { - return make_unique(b_engine, b_scene); - } - - return make_unique(); -} - -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(&shader_program_)); - } - - if (!shader_program_) { - LOG(ERROR) << "Error retrieving shader program for display space shader."; - } - - return shader_program_; -} - -/* -------------------------------------------------------------------- - * BlenderGPUDisplay. - */ - -BlenderGPUDisplay::BlenderGPUDisplay(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(); -} - -BlenderGPUDisplay::~BlenderGPUDisplay() -{ - gl_resources_destroy(); -} - -/* -------------------------------------------------------------------- - * Update procedure. - */ - -bool BlenderGPUDisplay::do_update_begin(const GPUDisplayParams ¶ms, - int texture_width, - int texture_height) -{ - /* Note that it's the responsibility of BlenderGPUDisplay 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 at the GPU display level, 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; - - return true; -} - -void BlenderGPUDisplay::do_update_end() -{ - gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - - gl_context_disable(); -} - -/* -------------------------------------------------------------------- - * Texture update from CPU buffer. - */ - -void BlenderGPUDisplay::do_copy_pixels_to_texture( - const half4 *rgba_pixels, int texture_x, int texture_y, int pixels_width, int pixels_height) -{ - /* This call copies pixels to a Pixel Buffer Object (PBO) which is much cheaper from CPU time - * point of view than to copy data directly to the OpenGL texture. - * - * The possible downside of this approach is that it might require a higher peak memory when - * doing partial updates of the texture (although, in practice even partial updates might peak - * with a full-frame buffer stored on the CPU if the GPU is currently occupied). */ - - half4 *mapped_rgba_pixels = map_texture_buffer(); - if (!mapped_rgba_pixels) { - return; - } - - if (texture_x == 0 && texture_y == 0 && pixels_width == texture_.width && - pixels_height == texture_.height) { - const size_t size_in_bytes = sizeof(half4) * texture_.width * texture_.height; - memcpy(mapped_rgba_pixels, rgba_pixels, size_in_bytes); - } - else { - const half4 *rgba_row = rgba_pixels; - half4 *mapped_rgba_row = mapped_rgba_pixels + texture_y * texture_.width + texture_x; - for (int y = 0; y < pixels_height; - ++y, rgba_row += pixels_width, mapped_rgba_row += texture_.width) { - memcpy(mapped_rgba_row, rgba_row, sizeof(half4) * pixels_width); - } - } - - unmap_texture_buffer(); -} - -/* -------------------------------------------------------------------- - * Texture buffer mapping. - */ - -half4 *BlenderGPUDisplay::do_map_texture_buffer() -{ - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); - - half4 *mapped_rgba_pixels = reinterpret_cast( - glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); - if (!mapped_rgba_pixels) { - LOG(ERROR) << "Error mapping BlenderGPUDisplay pixel buffer object."; - } - - if (texture_.need_clear) { - const int64_t texture_width = texture_.width; - const int64_t texture_height = texture_.height; - memset(reinterpret_cast(mapped_rgba_pixels), - 0, - texture_width * texture_height * sizeof(half4)); - texture_.need_clear = false; - } - - return mapped_rgba_pixels; -} - -void BlenderGPUDisplay::do_unmap_texture_buffer() -{ - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); -} - -/* -------------------------------------------------------------------- - * Graphics interoperability. - */ - -DeviceGraphicsInteropDestination BlenderGPUDisplay::do_graphics_interop_get() -{ - DeviceGraphicsInteropDestination 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 BlenderGPUDisplay::graphics_interop_activate() -{ - gl_context_enable(); -} - -void BlenderGPUDisplay::graphics_interop_deactivate() -{ - gl_context_disable(); -} - -/* -------------------------------------------------------------------- - * Drawing. - */ - -void BlenderGPUDisplay::clear() -{ - texture_.need_clear = true; -} - -void BlenderGPUDisplay::set_zoom(float zoom_x, float zoom_y) -{ - zoom_ = make_float2(zoom_x, zoom_y); -} - -void BlenderGPUDisplay::do_draw(const GPUDisplayParams ¶ms) -{ - /* 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. - * Watchout 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 BlenderGPUDisplay::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(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 BlenderGPUDisplay::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(b_engine_.ptr.data)); - return true; -} - -void BlenderGPUDisplay::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(b_engine_.ptr.data)); -} - -void BlenderGPUDisplay::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 BlenderGPUDisplay::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 BlenderGPUDisplay::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 BlenderGPUDisplay::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 BlenderGPUDisplay::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 BlenderGPUDisplay::vertex_buffer_update(const GPUDisplayParams ¶ms) -{ - /* 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(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); - if (!vpointer) { - return; - } - - vpointer[0] = 0.0f; - vpointer[1] = 0.0f; - vpointer[2] = params.offset.x; - vpointer[3] = params.offset.y; - - vpointer[4] = 1.0f; - vpointer[5] = 0.0f; - vpointer[6] = (float)params.size.x + params.offset.x; - vpointer[7] = params.offset.y; - - vpointer[8] = 1.0f; - vpointer[9] = 1.0f; - vpointer[10] = (float)params.size.x + params.offset.x; - vpointer[11] = (float)params.size.y + params.offset.y; - - vpointer[12] = 0.0f; - vpointer[13] = 1.0f; - vpointer[14] = params.offset.x; - vpointer[15] = (float)params.size.y + params.offset.y; - - glUnmapBuffer(GL_ARRAY_BUFFER); -} - -CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_gpu_display.h b/intern/cycles/blender/blender_gpu_display.h deleted file mode 100644 index 89420567037..00000000000 --- a/intern/cycles/blender/blender_gpu_display.h +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include "MEM_guardedalloc.h" - -#include "RNA_blender_cpp.h" - -#include "render/gpu_display.h" -#include "util/util_unique_ptr.h" - -CCL_NAMESPACE_BEGIN - -/* Base class of shader used for GPU display rendering. */ -class BlenderDisplayShader { - public: - static constexpr const char *position_attribute_name = "pos"; - static constexpr const char *tex_coord_attribute_name = "texCoord"; - - /* Create shader implementation suitable for the given render engine and scene configuration. */ - static unique_ptr create(BL::RenderEngine &b_engine, BL::Scene &b_scene); - - BlenderDisplayShader() = default; - virtual ~BlenderDisplayShader() = default; - - virtual void bind(int width, int height) = 0; - virtual void unbind() = 0; - - /* Get attribute location for position and texture coordinate respectively. - * NOTE: The shader needs to be bound to have access to those. */ - virtual int get_position_attrib_location(); - virtual int get_tex_coord_attrib_location(); - - protected: - /* Get program of this display shader. - * NOTE: The shader needs to be bound to have access to this. */ - virtual uint get_shader_program() = 0; - - /* Cached values of various OpenGL resources. */ - int position_attribute_location_ = -1; - int tex_coord_attribute_location_ = -1; -}; - -/* Implementation of display rendering shader used in the case when render engine does not support - * display space shader. */ -class BlenderFallbackDisplayShader : public BlenderDisplayShader { - public: - virtual void bind(int width, int height) override; - virtual void unbind() override; - - protected: - virtual uint get_shader_program() override; - - void create_shader_if_needed(); - void destroy_shader(); - - uint shader_program_ = 0; - int image_texture_location_ = -1; - int fullscreen_location_ = -1; - - /* Shader compilation attempted. Which means, that if the shader program is 0 then compilation or - * linking has failed. Do not attempt to re-compile the shader. */ - bool shader_compile_attempted_ = false; -}; - -class BlenderDisplaySpaceShader : public BlenderDisplayShader { - public: - BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, BL::Scene &b_scene); - - virtual void bind(int width, int height) override; - virtual void unbind() override; - - protected: - virtual uint get_shader_program() override; - - BL::RenderEngine b_engine_; - BL::Scene &b_scene_; - - /* Cached values of various OpenGL resources. */ - uint shader_program_ = 0; -}; - -/* GPU display implementation which is specific for Blender viewport integration. */ -class BlenderGPUDisplay : public GPUDisplay { - public: - BlenderGPUDisplay(BL::RenderEngine &b_engine, BL::Scene &b_scene); - ~BlenderGPUDisplay(); - - virtual void graphics_interop_activate() override; - virtual void graphics_interop_deactivate() override; - - virtual void clear() override; - - void set_zoom(float zoom_x, float zoom_y); - - protected: - virtual bool do_update_begin(const GPUDisplayParams ¶ms, - int texture_width, - int texture_height) override; - virtual void do_update_end() override; - - virtual void do_copy_pixels_to_texture(const half4 *rgba_pixels, - int texture_x, - int texture_y, - int pixels_width, - int pixels_height) override; - virtual void do_draw(const GPUDisplayParams ¶ms) override; - - virtual half4 *do_map_texture_buffer() override; - virtual void do_unmap_texture_buffer() override; - - virtual DeviceGraphicsInteropDestination do_graphics_interop_get() override; - - /* Helper function which allocates new GPU context. */ - void gl_context_create(); - bool gl_context_enable(); - 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 GPUDisplayParams ¶ms); - - BL::RenderEngine b_engine_; - - /* OpenGL context which is used the render engine doesn't have its own. */ - void *gl_context_ = nullptr; - /* The when Blender RenderEngine side context is not available and the GPUDisplay is to create - * its own context. */ - bool use_gl_context_ = false; - /* 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 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; - } texture_; - - unique_ptr 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; - - void *gl_render_sync_ = nullptr; - void *gl_upload_sync_ = nullptr; - - float2 zoom_ = make_float2(1.0f, 1.0f); -}; - -CCL_NAMESPACE_END diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp index 88edc7eafe7..1a42456eda0 100644 --- a/intern/cycles/blender/blender_session.cpp +++ b/intern/cycles/blender/blender_session.cpp @@ -42,7 +42,7 @@ #include "util/util_progress.h" #include "util/util_time.h" -#include "blender/blender_gpu_display.h" +#include "blender/blender_display_driver.h" #include "blender/blender_session.h" #include "blender/blender_sync.h" #include "blender/blender_util.h" @@ -159,9 +159,10 @@ void BlenderSession::create_session() /* Create GPU display. */ if (!b_engine.is_preview() && !headless) { - unique_ptr gpu_display = make_unique(b_engine, b_scene); - gpu_display_ = gpu_display.get(); - session->set_gpu_display(move(gpu_display)); + unique_ptr display_driver = make_unique(b_engine, + b_scene); + display_driver_ = display_driver.get(); + session->set_display_driver(move(display_driver)); } /* Viewport and preview (as in, material preview) does not do tiled rendering, so can inform @@ -446,7 +447,7 @@ void BlenderSession::render(BL::Depsgraph &b_depsgraph_) /* Use final write for preview renders, otherwise render result wouldn't be be updated on Blender * side. */ - /* TODO(sergey): Investigate whether GPUDisplay can be used for the preview as well. */ + /* TODO(sergey): Investigate whether DisplayDriver can be used for the preview as well. */ if (b_engine.is_preview()) { session->update_render_tile_cb = [&]() { write_render_tile(); }; } @@ -708,7 +709,7 @@ void BlenderSession::bake(BL::Depsgraph &b_depsgraph_, session->read_render_tile_cb = [&]() { read_render_tile(); }; session->write_render_tile_cb = [&]() { write_render_tile(); }; - session->set_gpu_display(nullptr); + session->set_display_driver(nullptr); if (!session->progress.get_cancel()) { /* Sync scene. */ @@ -895,7 +896,7 @@ void BlenderSession::draw(BL::SpaceImageEditor &space_image) } BL::Array zoom = space_image.zoom(); - gpu_display_->set_zoom(zoom[0], zoom[1]); + display_driver_->set_zoom(zoom[0], zoom[1]); session->draw(); } diff --git a/intern/cycles/blender/blender_session.h b/intern/cycles/blender/blender_session.h index 58683ee07a1..1ca8fdf87d0 100644 --- a/intern/cycles/blender/blender_session.h +++ b/intern/cycles/blender/blender_session.h @@ -29,7 +29,7 @@ CCL_NAMESPACE_BEGIN -class BlenderGPUDisplay; +class BlenderDisplayDriver; class BlenderSync; class ImageMetaData; class Scene; @@ -164,8 +164,8 @@ class BlenderSession { int last_pass_index = -1; } draw_state_; - /* NOTE: The BlenderSession references the GPU display. */ - BlenderGPUDisplay *gpu_display_ = nullptr; + /* NOTE: The BlenderSession references the display driver. */ + BlenderDisplayDriver *display_driver_ = nullptr; vector full_buffer_files_; }; -- cgit v1.2.3