Changeset View
Standalone View
intern/cycles/blender/display_driver.cpp
| /* SPDX-License-Identifier: Apache-2.0 | /* SPDX-License-Identifier: Apache-2.0 | |||||||||||
| * Copyright 2021-2022 Blender Foundation */ | * Copyright 2021-2022 Blender Foundation */ | |||||||||||
| #include "blender/display_driver.h" | #include "blender/display_driver.h" | |||||||||||
| #include "device/device.h" | #include "device/device.h" | |||||||||||
| #include "util/log.h" | #include "util/log.h" | |||||||||||
| #include "util/math.h" | ||||||||||||
| #include "util/opengl.h" | #include "util/opengl.h" | |||||||||||
| #include "GPU_platform.h" | #include "GPU_context.h" | |||||||||||
| #include "GPU_immediate.h" | ||||||||||||
| #include "GPU_shader.h" | ||||||||||||
| #include "GPU_state.h" | ||||||||||||
| #include "GPU_texture.h" | ||||||||||||
| #include "RE_engine.h" | #include "RE_engine.h" | |||||||||||
| CCL_NAMESPACE_BEGIN | CCL_NAMESPACE_BEGIN | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * BlenderDisplayShader. | * BlenderDisplayShader. | |||||||||||
| */ | */ | |||||||||||
| unique_ptr<BlenderDisplayShader> BlenderDisplayShader::create(BL::RenderEngine &b_engine, | unique_ptr<BlenderDisplayShader> BlenderDisplayShader::create(BL::RenderEngine &b_engine, | |||||||||||
| BL::Scene &b_scene) | BL::Scene &b_scene) | |||||||||||
| { | { | |||||||||||
| if (b_engine.support_display_space_shader(b_scene)) { | if (b_engine.support_display_space_shader(b_scene)) { | |||||||||||
| return make_unique<BlenderDisplaySpaceShader>(b_engine, b_scene); | return make_unique<BlenderDisplaySpaceShader>(b_engine, b_scene); | |||||||||||
| } | } | |||||||||||
| return make_unique<BlenderFallbackDisplayShader>(); | return make_unique<BlenderFallbackDisplayShader>(); | |||||||||||
| } | } | |||||||||||
| int BlenderDisplayShader::get_position_attrib_location() | int BlenderDisplayShader::get_position_attrib_location() | |||||||||||
| { | { | |||||||||||
| if (position_attribute_location_ == -1) { | if (position_attribute_location_ == -1) { | |||||||||||
| const uint shader_program = get_shader_program(); | GPUShader *shader_program = get_shader_program(); | |||||||||||
| position_attribute_location_ = glGetAttribLocation(shader_program, position_attribute_name); | position_attribute_location_ = GPU_shader_get_attribute(shader_program, | |||||||||||
| position_attribute_name); | ||||||||||||
| } | } | |||||||||||
| return position_attribute_location_; | return position_attribute_location_; | |||||||||||
| } | } | |||||||||||
| int BlenderDisplayShader::get_tex_coord_attrib_location() | int BlenderDisplayShader::get_tex_coord_attrib_location() | |||||||||||
| { | { | |||||||||||
| if (tex_coord_attribute_location_ == -1) { | if (tex_coord_attribute_location_ == -1) { | |||||||||||
| const uint shader_program = get_shader_program(); | GPUShader *shader_program = get_shader_program(); | |||||||||||
| tex_coord_attribute_location_ = glGetAttribLocation(shader_program, tex_coord_attribute_name); | tex_coord_attribute_location_ = GPU_shader_get_attribute(shader_program, | |||||||||||
| tex_coord_attribute_name); | ||||||||||||
| } | } | |||||||||||
| return tex_coord_attribute_location_; | return tex_coord_attribute_location_; | |||||||||||
| } | } | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * BlenderFallbackDisplayShader. | * BlenderFallbackDisplayShader. | |||||||||||
| */ | */ | |||||||||||
| Show All 22 Lines | static const char *FALLBACK_FRAGMENT_SHADER = | |||||||||||
| "in vec2 texCoord_interp;\n" | "in vec2 texCoord_interp;\n" | |||||||||||
| "out vec4 fragColor;\n" | "out vec4 fragColor;\n" | |||||||||||
| "\n" | "\n" | |||||||||||
| "void main()\n" | "void main()\n" | |||||||||||
| "{\n" | "{\n" | |||||||||||
| " fragColor = texture(image_texture, texCoord_interp);\n" | " fragColor = texture(image_texture, texCoord_interp);\n" | |||||||||||
| "}\n\0"; | "}\n\0"; | |||||||||||
| static void shader_print_errors(const char *task, const char *log, const char *code) | static GPUShader *compile_fallback_shader(void) | |||||||||||
| { | { | |||||||||||
| LOG(ERROR) << "Shader: " << task << " error:"; | /* NOTE: Compilation errors are logged to console. */ | |||||||||||
| LOG(ERROR) << "===== shader string ===="; | GPUShader *shader = GPU_shader_create(FALLBACK_VERTEX_SHADER, | |||||||||||
| FALLBACK_FRAGMENT_SHADER, | ||||||||||||
| stringstream stream(code); | nullptr, | |||||||||||
| string partial; | nullptr, | |||||||||||
| nullptr, | ||||||||||||
| int line = 1; | "FallbackCyclesBlitShader"); | |||||||||||
| while (getline(stream, partial, '\n')) { | return shader; | |||||||||||
sergey: `Compilation errors are logged to console` ? | ||||||||||||
Not Done Inline ActionsGPU module shader compilation should have its own error logger via gl_shader_log.cc for OpenGL. Would this suffice, or is other logging preferred? Thanks! MichaelPW: GPU module shader compilation should have its own error logger via gl_shader_log.cc for OpenGL. | ||||||||||||
Not Done Inline ActionsUse the GPU module's own error logging is fine, since this is code part of Blender. brecht: Use the GPU module's own error logging is fine, since this is code part of Blender. | ||||||||||||
| 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) | GPUShader *BlenderFallbackDisplayShader::bind(int width, int height) | |||||||||||
| { | { | |||||||||||
| create_shader_if_needed(); | create_shader_if_needed(); | |||||||||||
| if (!shader_program_) { | if (!shader_program_) { | |||||||||||
| return; | return nullptr; | |||||||||||
| } | } | |||||||||||
| glUseProgram(shader_program_); | /* Bind shader now to enable uniform assignment. */ | |||||||||||
| glUniform1i(image_texture_location_, 0); | GPU_shader_bind(shader_program_); | |||||||||||
| glUniform2f(fullscreen_location_, width, height); | GPU_shader_uniform_int(shader_program_, image_texture_location_, 0); | |||||||||||
| float size[2]; | ||||||||||||
| size[0] = width; | ||||||||||||
| size[1] = height; | ||||||||||||
| GPU_shader_uniform_vector(shader_program_, fullscreen_location_, 2, 1, size); | ||||||||||||
| return shader_program_; | ||||||||||||
| } | } | |||||||||||
| void BlenderFallbackDisplayShader::unbind() | void BlenderFallbackDisplayShader::unbind() | |||||||||||
| { | { | |||||||||||
| GPU_shader_unbind(); | ||||||||||||
Not Done Inline ActionsIf the shader was bound using GPU_shader_bind one would intuitively expect GPU_shader_unbind be used to unbind it. sergey: If the shader was bound using `GPU_shader_bind` one would intuitively expect… | ||||||||||||
| } | } | |||||||||||
| uint BlenderFallbackDisplayShader::get_shader_program() | GPUShader *BlenderFallbackDisplayShader::get_shader_program() | |||||||||||
| { | { | |||||||||||
| return shader_program_; | return shader_program_; | |||||||||||
| } | } | |||||||||||
| void BlenderFallbackDisplayShader::create_shader_if_needed() | void BlenderFallbackDisplayShader::create_shader_if_needed() | |||||||||||
| { | { | |||||||||||
| if (shader_program_ || shader_compile_attempted_) { | if (shader_program_ || shader_compile_attempted_) { | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| shader_compile_attempted_ = true; | shader_compile_attempted_ = true; | |||||||||||
| shader_program_ = compile_fallback_shader(); | shader_program_ = compile_fallback_shader(); | |||||||||||
| if (!shader_program_) { | if (!shader_program_) { | |||||||||||
| LOG(ERROR) << "Failed to compile fallback shader"; | ||||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| glUseProgram(shader_program_); | image_texture_location_ = GPU_shader_get_uniform(shader_program_, "image_texture"); | |||||||||||
| image_texture_location_ = glGetUniformLocation(shader_program_, "image_texture"); | ||||||||||||
| if (image_texture_location_ < 0) { | if (image_texture_location_ < 0) { | |||||||||||
| LOG(ERROR) << "Shader doesn't contain the 'image_texture' uniform."; | LOG(ERROR) << "Shader doesn't contain the 'image_texture' uniform."; | |||||||||||
| destroy_shader(); | destroy_shader(); | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| fullscreen_location_ = glGetUniformLocation(shader_program_, "fullscreen"); | fullscreen_location_ = GPU_shader_get_uniform(shader_program_, "fullscreen"); | |||||||||||
| if (fullscreen_location_ < 0) { | if (fullscreen_location_ < 0) { | |||||||||||
| LOG(ERROR) << "Shader doesn't contain the 'fullscreen' uniform."; | LOG(ERROR) << "Shader doesn't contain the 'fullscreen' uniform."; | |||||||||||
| destroy_shader(); | destroy_shader(); | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| } | } | |||||||||||
| void BlenderFallbackDisplayShader::destroy_shader() | void BlenderFallbackDisplayShader::destroy_shader() | |||||||||||
| { | { | |||||||||||
| glDeleteProgram(shader_program_); | if (shader_program_) { | |||||||||||
| shader_program_ = 0; | GPU_shader_free(shader_program_); | |||||||||||
| shader_program_ = nullptr; | ||||||||||||
| } | ||||||||||||
| } | } | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * BlenderDisplaySpaceShader. | * BlenderDisplaySpaceShader. | |||||||||||
| */ | */ | |||||||||||
| BlenderDisplaySpaceShader::BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, | BlenderDisplaySpaceShader::BlenderDisplaySpaceShader(BL::RenderEngine &b_engine, | |||||||||||
| BL::Scene &b_scene) | BL::Scene &b_scene) | |||||||||||
| : b_engine_(b_engine), b_scene_(b_scene) | : b_engine_(b_engine), b_scene_(b_scene) | |||||||||||
| { | { | |||||||||||
| DCHECK(b_engine_.support_display_space_shader(b_scene_)); | DCHECK(b_engine_.support_display_space_shader(b_scene_)); | |||||||||||
| } | } | |||||||||||
| void BlenderDisplaySpaceShader::bind(int /*width*/, int /*height*/) | GPUShader *BlenderDisplaySpaceShader::bind(int /*width*/, int /*height*/) | |||||||||||
| { | { | |||||||||||
| b_engine_.bind_display_space_shader(b_scene_); | b_engine_.bind_display_space_shader(b_scene_); | |||||||||||
| return GPU_shader_get_bound(); | ||||||||||||
| } | } | |||||||||||
| void BlenderDisplaySpaceShader::unbind() | void BlenderDisplaySpaceShader::unbind() | |||||||||||
| { | { | |||||||||||
| b_engine_.unbind_display_space_shader(); | b_engine_.unbind_display_space_shader(); | |||||||||||
| } | } | |||||||||||
| uint BlenderDisplaySpaceShader::get_shader_program() | GPUShader *BlenderDisplaySpaceShader::get_shader_program() | |||||||||||
| { | { | |||||||||||
| if (!shader_program_) { | if (!shader_program_) { | |||||||||||
| glGetIntegerv(GL_CURRENT_PROGRAM, reinterpret_cast<int *>(&shader_program_)); | shader_program_ = GPU_shader_get_bound(); | |||||||||||
| } | } | |||||||||||
| if (!shader_program_) { | if (!shader_program_) { | |||||||||||
| LOG(ERROR) << "Error retrieving shader program for display space shader."; | LOG(ERROR) << "Error retrieving shader program for display space shader."; | |||||||||||
| } | } | |||||||||||
| return shader_program_; | return shader_program_; | |||||||||||
| } | } | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * DrawTile. | * DrawTile. | |||||||||||
| */ | */ | |||||||||||
| /* Higher level representation of a texture from the graphics library. */ | /* Higher level representation of a texture from the graphics library. */ | |||||||||||
| class GLTexture { | class DisplayGPUTexture { | |||||||||||
Not Done Inline ActionsI do not see the point of such shortening. sergey: I do not see the point of such shortening. | ||||||||||||
| public: | public: | |||||||||||
| /* Global counter for all allocated OpenGL textures used by instances of this class. */ | /* Global counter for all allocated GPUTextures used by instances of this class. */ | |||||||||||
| static inline std::atomic<int> num_used = 0; | static inline std::atomic<int> num_used = 0; | |||||||||||
| GLTexture() = default; | DisplayGPUTexture() = default; | |||||||||||
| ~GLTexture() | ~DisplayGPUTexture() | |||||||||||
| { | { | |||||||||||
| assert(gl_id == 0); | assert(gpu_texture == nullptr); | |||||||||||
| } | } | |||||||||||
| GLTexture(const GLTexture &other) = delete; | DisplayGPUTexture(const DisplayGPUTexture &other) = delete; | |||||||||||
| GLTexture &operator=(GLTexture &other) = delete; | DisplayGPUTexture &operator=(DisplayGPUTexture &other) = delete; | |||||||||||
| GLTexture(GLTexture &&other) noexcept | DisplayGPUTexture(DisplayGPUTexture &&other) noexcept | |||||||||||
| : gl_id(other.gl_id), width(other.width), height(other.height) | : gpu_texture(other.gpu_texture), width(other.width), height(other.height) | |||||||||||
| { | { | |||||||||||
| other.reset(); | other.reset(); | |||||||||||
| } | } | |||||||||||
| GLTexture &operator=(GLTexture &&other) | DisplayGPUTexture &operator=(DisplayGPUTexture &&other) | |||||||||||
| { | { | |||||||||||
| if (this == &other) { | if (this == &other) { | |||||||||||
| return *this; | return *this; | |||||||||||
| } | } | |||||||||||
| gl_id = other.gl_id; | gpu_texture = other.gpu_texture; | |||||||||||
| width = other.width; | width = other.width; | |||||||||||
| height = other.height; | height = other.height; | |||||||||||
| other.reset(); | other.reset(); | |||||||||||
| return *this; | return *this; | |||||||||||
| } | } | |||||||||||
| bool gl_resources_ensure() | bool gpu_resources_ensure() | |||||||||||
| { | { | |||||||||||
| if (gl_id) { | if (gpu_texture) { | |||||||||||
| return true; | return true; | |||||||||||
| } | } | |||||||||||
| /* Create texture. */ | /* Texture must have a minimum size of 1x1. */ | |||||||||||
| glGenTextures(1, &gl_id); | gpu_texture = GPU_texture_create_2d( | |||||||||||
| if (!gl_id) { | "CyclesBlitTexture", max(width, 1), max(height, 1), 1, GPU_RGBA16F, nullptr); | |||||||||||
Not Done Inline ActionsUse min and max from the Cycles math.h utilities. sergey: Use `min` and `max` from the Cycles `math.h` utilities. | ||||||||||||
| if (!gpu_texture) { | ||||||||||||
| LOG(ERROR) << "Error creating texture."; | LOG(ERROR) << "Error creating texture."; | |||||||||||
| return false; | return false; | |||||||||||
| } | } | |||||||||||
| /* Configure the texture. */ | GPU_texture_filter_mode(gpu_texture, false); | |||||||||||
| glActiveTexture(GL_TEXTURE0); | GPU_texture_wrap_mode(gpu_texture, false, true); | |||||||||||
| 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; | ++num_used; | |||||||||||
| return true; | return true; | |||||||||||
| } | } | |||||||||||
| void gl_resources_destroy() | void gpu_resources_destroy() | |||||||||||
| { | { | |||||||||||
| if (!gl_id) { | if (gpu_texture == nullptr) { | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| glDeleteTextures(1, &gl_id); | GPU_TEXTURE_FREE_SAFE(gpu_texture); | |||||||||||
| reset(); | reset(); | |||||||||||
| --num_used; | --num_used; | |||||||||||
| } | } | |||||||||||
| /* OpenGL resource IDs of the texture. | void ensure_size(uint texture_width, uint texture_height) | |||||||||||
| { | ||||||||||||
| if (width != texture_width || height != texture_height) { | ||||||||||||
| gpu_resources_destroy(); | ||||||||||||
| width = texture_width; | ||||||||||||
| height = texture_height; | ||||||||||||
| gpu_resources_ensure(); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| /* Texture resource allocated by the GPU module. | ||||||||||||
Not Done Inline Actions
This is not really an ID anymore. sergey: This is not really an ID anymore. | ||||||||||||
| * | * | |||||||||||
| * NOTE: Allocated on the render engine's context. */ | * NOTE: Allocated on the render engine's context. */ | |||||||||||
| uint gl_id = 0; | GPUTexture *gpu_texture = nullptr; | |||||||||||
Not Done Inline Actions
sergey: | ||||||||||||
| /* Dimensions of the texture in pixels. */ | /* Dimensions of the texture in pixels. */ | |||||||||||
| int width = 0; | int width = 0; | |||||||||||
| int height = 0; | int height = 0; | |||||||||||
| protected: | protected: | |||||||||||
| void reset() | void reset() | |||||||||||
| { | { | |||||||||||
| gl_id = 0; | gpu_texture = nullptr; | |||||||||||
| width = 0; | width = 0; | |||||||||||
| height = 0; | height = 0; | |||||||||||
| } | } | |||||||||||
| }; | }; | |||||||||||
| /* Higher level representation of a Pixel Buffer Object (PBO) from the graphics library. */ | /* Higher level representation of a Pixel Buffer Object (PBO) from the graphics library. */ | |||||||||||
| class GLPixelBufferObject { | class DisplayGPUPixelBuffer { | |||||||||||
Not Done Inline ActionsDon't just shorten existing variables. sergey: Don't just shorten existing variables.
In general, don't value saving horizontal space over… | ||||||||||||
| public: | public: | |||||||||||
| /* Global counter for all allocated OpenGL PBOs used by instances of this class. */ | /* Global counter for all allocated GPU module PBOs used by instances of this class. */ | |||||||||||
| static inline std::atomic<int> num_used = 0; | static inline std::atomic<int> num_used = 0; | |||||||||||
| GLPixelBufferObject() = default; | DisplayGPUPixelBuffer() = default; | |||||||||||
| ~GLPixelBufferObject() | ~DisplayGPUPixelBuffer() | |||||||||||
| { | { | |||||||||||
| assert(gl_id == 0); | assert(gpu_pixel_buffer == nullptr); | |||||||||||
| } | } | |||||||||||
| GLPixelBufferObject(const GLPixelBufferObject &other) = delete; | DisplayGPUPixelBuffer(const DisplayGPUPixelBuffer &other) = delete; | |||||||||||
| GLPixelBufferObject &operator=(GLPixelBufferObject &other) = delete; | DisplayGPUPixelBuffer &operator=(DisplayGPUPixelBuffer &other) = delete; | |||||||||||
| GLPixelBufferObject(GLPixelBufferObject &&other) noexcept | DisplayGPUPixelBuffer(DisplayGPUPixelBuffer &&other) noexcept | |||||||||||
| : gl_id(other.gl_id), width(other.width), height(other.height) | : gpu_pixel_buffer(other.gpu_pixel_buffer), width(other.width), height(other.height) | |||||||||||
| { | { | |||||||||||
| other.reset(); | other.reset(); | |||||||||||
| } | } | |||||||||||
| GLPixelBufferObject &operator=(GLPixelBufferObject &&other) | DisplayGPUPixelBuffer &operator=(DisplayGPUPixelBuffer &&other) | |||||||||||
| { | { | |||||||||||
| if (this == &other) { | if (this == &other) { | |||||||||||
| return *this; | return *this; | |||||||||||
| } | } | |||||||||||
| gl_id = other.gl_id; | gpu_pixel_buffer = other.gpu_pixel_buffer; | |||||||||||
| width = other.width; | width = other.width; | |||||||||||
| height = other.height; | height = other.height; | |||||||||||
| other.reset(); | other.reset(); | |||||||||||
| return *this; | return *this; | |||||||||||
| } | } | |||||||||||
| bool gl_resources_ensure() | void ensure_size(uint new_width, uint new_height) | |||||||||||
| { | { | |||||||||||
| if (gl_id) { | size_t required_size = sizeof(half4) * new_width * new_height * 4; | |||||||||||
| return true; | ||||||||||||
Not Done Inline ActionsWhile currently this is OK, more correct would be be to use size_t instead of uint here. sergey: While currently this is OK, more correct would be be to use `size_t` instead of `uint` here. | ||||||||||||
Not Done Inline ActionsWill update, thanks MichaelPW: Will update, thanks | ||||||||||||
| if (gpu_pixel_buffer) { | ||||||||||||
Not Done Inline ActionsUse max from the ccl namespace. sergey: Use `max` from the `ccl` namespace.
Within the current state of the code it will avoid implicit… | ||||||||||||
| if (new_width != width || new_height != height || | ||||||||||||
| GPU_pixel_buffer_size(gpu_pixel_buffer) < required_size) { | ||||||||||||
| GPU_pixel_buffer_free(gpu_pixel_buffer); | ||||||||||||
| gpu_pixel_buffer = nullptr; | ||||||||||||
| } | ||||||||||||
Not Done Inline ActionsWhy the 256 bytes is so important? What does it mean "to enable allocation" ? sergey: Why the 256 bytes is so important? What does it mean "to enable allocation" ? | ||||||||||||
Not Done Inline ActionsFor Metal, minimum buffer allocation size generally needs to be 256 bytes. This detail could be moved to the backend, but the key is to ensure that we can create a valid allocation, if the is what the remaining code expects. This is also a catch-all against a zero-sized guard, to ensure that the path functions as expected. The alternative here would be to guard creation if either dimension ends up as zero. Let me know if there is a preference either way? MichaelPW: For Metal, minimum buffer allocation size generally needs to be 256 bytes. This detail could be… | ||||||||||||
Not Done Inline ActionsEnsuring GPU_pixel_buffer allocates at least 256 in the backend seems needed for consistency between platforms then, since small buffers could run into the same issue, not just zero sized ones. If we need additional changes to reject zero sized buffers in the GPU API I don't know. The main thing is that we either reject or gracefully handle such cases in the GPU API, the same way on platforms. I personally don't have a preference, not familiar enough with the work on the GPU API. brecht: Ensuring `GPU_pixel_buffer` allocates at least 256 in the backend seems needed for consistency… | ||||||||||||
| } | } | |||||||||||
| glGenBuffers(1, &gl_id); | /* Update size. */ | |||||||||||
| if (!gl_id) { | width = new_width; | |||||||||||
| height = new_height; | ||||||||||||
| /* Create pixel buffer if not already created. */ | ||||||||||||
| if (!gpu_pixel_buffer) { | ||||||||||||
| gpu_pixel_buffer = GPU_pixel_buffer_create(required_size); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| bool gpu_resources_ensure() | ||||||||||||
| { | ||||||||||||
| /* Create pixel buffer using current size. */ | ||||||||||||
| ensure_size(width, height); | ||||||||||||
| if (gpu_pixel_buffer == nullptr) { | ||||||||||||
| LOG(ERROR) << "Error creating texture pixel buffer object."; | LOG(ERROR) << "Error creating texture pixel buffer object."; | |||||||||||
| return false; | return false; | |||||||||||
| } | } | |||||||||||
| ++num_used; | ++num_used; | |||||||||||
| return true; | return true; | |||||||||||
| } | } | |||||||||||
| void gl_resources_destroy() | void gpu_resources_destroy() | |||||||||||
| { | { | |||||||||||
| if (!gl_id) { | if (!gpu_pixel_buffer) { | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| glDeleteBuffers(1, &gl_id); | GPU_pixel_buffer_free(gpu_pixel_buffer); | |||||||||||
| gpu_pixel_buffer = nullptr; | ||||||||||||
| reset(); | reset(); | |||||||||||
| --num_used; | --num_used; | |||||||||||
| } | } | |||||||||||
| /* OpenGL resource IDs of the PBO. | /* Pixel Buffer Object allocated by the GPU module. | |||||||||||
Not Done Inline Actions
sergey: | ||||||||||||
| * | * | |||||||||||
| * NOTE: Allocated on the render engine's context. */ | * NOTE: Allocated on the render engine's context. */ | |||||||||||
| uint gl_id = 0; | GPUPixelBuffer *gpu_pixel_buffer = nullptr; | |||||||||||
Not Done Inline Actions
sergey: | ||||||||||||
| /* Dimensions of the PBO. */ | /* Dimensions of the PBO. */ | |||||||||||
| int width = 0; | int width = 0; | |||||||||||
| int height = 0; | int height = 0; | |||||||||||
| protected: | protected: | |||||||||||
| void reset() | void reset() | |||||||||||
| { | { | |||||||||||
| gl_id = 0; | gpu_pixel_buffer = 0; | |||||||||||
| width = 0; | width = 0; | |||||||||||
| height = 0; | height = 0; | |||||||||||
| } | } | |||||||||||
| }; | }; | |||||||||||
| class DrawTile { | class DrawTile { | |||||||||||
| public: | public: | |||||||||||
| DrawTile() = default; | DrawTile() = default; | |||||||||||
| ~DrawTile() = default; | ~DrawTile() = default; | |||||||||||
| DrawTile(const DrawTile &other) = delete; | DrawTile(const DrawTile &other) = delete; | |||||||||||
| DrawTile &operator=(const DrawTile &other) = delete; | DrawTile &operator=(const DrawTile &other) = delete; | |||||||||||
| DrawTile(DrawTile &&other) noexcept = default; | DrawTile(DrawTile &&other) noexcept = default; | |||||||||||
| DrawTile &operator=(DrawTile &&other) = default; | DrawTile &operator=(DrawTile &&other) = default; | |||||||||||
| bool gl_resources_ensure() | bool gpu_resources_ensure() | |||||||||||
| { | { | |||||||||||
| if (!texture.gl_resources_ensure()) { | if (!texture.gpu_resources_ensure()) { | |||||||||||
| gl_resources_destroy(); | gpu_resources_destroy(); | |||||||||||
| return false; | return false; | |||||||||||
| } | } | |||||||||||
| return true; | return true; | |||||||||||
| } | } | |||||||||||
| void gl_resources_destroy() | void gpu_resources_destroy() | |||||||||||
| { | { | |||||||||||
| texture.gl_resources_destroy(); | texture.gpu_resources_destroy(); | |||||||||||
| } | } | |||||||||||
| inline bool ready_to_draw() const | inline bool ready_to_draw() const | |||||||||||
| { | { | |||||||||||
| return texture.gl_id != 0; | return texture.gpu_texture != 0; | |||||||||||
| } | } | |||||||||||
| /* Texture which contains pixels of the tile. */ | /* Texture which contains pixels of the tile. */ | |||||||||||
| GLTexture texture; | DisplayGPUTexture texture; | |||||||||||
| /* Display parameters the texture of this tile has been updated for. */ | /* Display parameters the texture of this tile has been updated for. */ | |||||||||||
| BlenderDisplayDriver::Params params; | BlenderDisplayDriver::Params params; | |||||||||||
| }; | }; | |||||||||||
| class DrawTileAndPBO { | class DrawTileAndPBO { | |||||||||||
| public: | public: | |||||||||||
| bool gl_resources_ensure() | bool gpu_resources_ensure() | |||||||||||
| { | { | |||||||||||
| if (!tile.gl_resources_ensure() || !buffer_object.gl_resources_ensure()) { | if (!tile.gpu_resources_ensure() || !buffer_object.gpu_resources_ensure()) { | |||||||||||
| gl_resources_destroy(); | gpu_resources_destroy(); | |||||||||||
| return false; | return false; | |||||||||||
| } | } | |||||||||||
| return true; | return true; | |||||||||||
| } | } | |||||||||||
| void gl_resources_destroy() | void gpu_resources_destroy() | |||||||||||
| { | { | |||||||||||
| tile.gl_resources_destroy(); | tile.gpu_resources_destroy(); | |||||||||||
| buffer_object.gl_resources_destroy(); | buffer_object.gpu_resources_destroy(); | |||||||||||
| } | } | |||||||||||
| DrawTile tile; | DrawTile tile; | |||||||||||
| GLPixelBufferObject buffer_object; | DisplayGPUPixelBuffer buffer_object; | |||||||||||
| bool need_update_texture_pixels = false; | bool need_update_texture_pixels = false; | |||||||||||
| }; | }; | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * BlenderDisplayDriver. | * BlenderDisplayDriver. | |||||||||||
| */ | */ | |||||||||||
| struct BlenderDisplayDriver::Tiles { | struct BlenderDisplayDriver::Tiles { | |||||||||||
| /* Resources of a tile which is being currently rendered. */ | /* Resources of a tile which is being currently rendered. */ | |||||||||||
| DrawTileAndPBO current_tile; | DrawTileAndPBO current_tile; | |||||||||||
| /* All tiles which rendering is finished and which content will not be changed. */ | /* All tiles which rendering is finished and which content will not be changed. */ | |||||||||||
| struct { | struct { | |||||||||||
| vector<DrawTile> tiles; | vector<DrawTile> tiles; | |||||||||||
| void gl_resources_destroy_and_clear() | void gl_resources_destroy_and_clear() | |||||||||||
| { | { | |||||||||||
| for (DrawTile &tile : tiles) { | for (DrawTile &tile : tiles) { | |||||||||||
| tile.gl_resources_destroy(); | tile.gpu_resources_destroy(); | |||||||||||
| } | } | |||||||||||
| tiles.clear(); | tiles.clear(); | |||||||||||
| } | } | |||||||||||
| } finished_tiles; | } finished_tiles; | |||||||||||
| /* OpenGL vertex buffer needed for drawing. */ | ||||||||||||
| uint gl_vertex_buffer = 0; | ||||||||||||
| bool gl_resources_ensure() | ||||||||||||
| { | ||||||||||||
| if (!gl_vertex_buffer) { | ||||||||||||
| glGenBuffers(1, &gl_vertex_buffer); | ||||||||||||
| if (!gl_vertex_buffer) { | ||||||||||||
| LOG(ERROR) << "Error allocating tile VBO."; | ||||||||||||
| return false; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| return true; | ||||||||||||
| } | ||||||||||||
| void gl_resources_destroy() | ||||||||||||
| { | ||||||||||||
| if (gl_vertex_buffer) { | ||||||||||||
| glDeleteBuffers(1, &gl_vertex_buffer); | ||||||||||||
| gl_vertex_buffer = 0; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| }; | }; | |||||||||||
Not Done Inline ActionsStill to be done in this patch, or later? brecht: Still to be done in this patch, or later? | ||||||||||||
Not Done Inline ActionsIs this something planned for this patch? sergey: Is this something planned for this patch? | ||||||||||||
Not Done Inline ActionsAh yes, this is possibly left open to discussion on whether GPUVertBuf via GPUBatch is preferred over GPU_Immediate? Given this is effectively just drawing a quad, the performance overhead of GPU_Immediate is negligible and this also simplifies the implementation detail. However, using GPUVertBuf may be preferred to keep the sentiment of the original code. Please let me know what the preference is? MichaelPW: Ah yes, this is possibly left open to discussion on whether GPUVertBuf via GPUBatch is… | ||||||||||||
Not Done Inline ActionsI think GPU immediate mode is fine if the code is simpler and performance is not impacted, as in this case. We just could not use it before with direct OpenGL calls. At least that is my understanding, unless @Clément Foucault (fclem) wants to move away from GPU_immediate.h in general. brecht: I think GPU immediate mode is fine if the code is simpler and performance is not impacted, as… | ||||||||||||
| BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, | BlenderDisplayDriver::BlenderDisplayDriver(BL::RenderEngine &b_engine, | |||||||||||
| BL::Scene &b_scene, | BL::Scene &b_scene, | |||||||||||
| const bool background) | const bool background) | |||||||||||
| : b_engine_(b_engine), | : b_engine_(b_engine), | |||||||||||
| background_(background), | background_(background), | |||||||||||
| display_shader_(BlenderDisplayShader::create(b_engine, b_scene)), | display_shader_(BlenderDisplayShader::create(b_engine, b_scene)), | |||||||||||
| tiles_(make_unique<Tiles>()) | tiles_(make_unique<Tiles>()) | |||||||||||
| Show All 30 Lines | ||||||||||||
| bool BlenderDisplayDriver::update_begin(const Params ¶ms, | bool BlenderDisplayDriver::update_begin(const Params ¶ms, | |||||||||||
| int texture_width, | int texture_width, | |||||||||||
| int texture_height) | int texture_height) | |||||||||||
| { | { | |||||||||||
| /* Note that it's the responsibility of BlenderDisplayDriver to ensure updating and drawing | /* 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. | * 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.gpu_context_lock. | * When enabling the OpenGL/GPU context, it uses an internal mutex lock DST.gpu_context_lock. | |||||||||||
| * This same lock is also held when do_draw() is called, which together ensure mutual | * This same lock is also held when do_draw() is called, which together ensure mutual | |||||||||||
| * exclusion. | * exclusion. | |||||||||||
| * | * | |||||||||||
| * This locking is not performed on the Cycles side, because that would cause lock inversion. */ | * This locking is not performed on the Cycles side, because that would cause lock inversion. */ | |||||||||||
| if (!gpu_context_enable()) { | if (!gpu_context_enable()) { | |||||||||||
| return false; | return false; | |||||||||||
| } | } | |||||||||||
| if (gl_render_sync_) { | GPU_fence_wait(gpu_render_sync_); | |||||||||||
Not Done Inline ActionsCan become simply GPU_fence_wait(gpu_render_sync_); (without check) ? sergey: Can become simply `GPU_fence_wait(gpu_render_sync_);` (without check) ? | ||||||||||||
| glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED); | ||||||||||||
| } | ||||||||||||
| DrawTile ¤t_tile = tiles_->current_tile.tile; | DrawTile ¤t_tile = tiles_->current_tile.tile; | |||||||||||
| GLPixelBufferObject ¤t_tile_buffer_object = tiles_->current_tile.buffer_object; | DisplayGPUPixelBuffer ¤t_tile_buffer_object = tiles_->current_tile.buffer_object; | |||||||||||
| /* Clear storage of all finished tiles when display clear is requested. | /* 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. | * 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. */ | * It also makes the logic reliable from the whether drawing did happen or not point of view. */ | |||||||||||
| if (need_clear_) { | if (need_clear_) { | |||||||||||
| tiles_->finished_tiles.gl_resources_destroy_and_clear(); | tiles_->finished_tiles.gl_resources_destroy_and_clear(); | |||||||||||
| need_clear_ = false; | need_clear_ = false; | |||||||||||
| } | } | |||||||||||
| if (!tiles_->gl_resources_ensure()) { | if (!tiles_->current_tile.gpu_resources_ensure()) { | |||||||||||
| tiles_->gl_resources_destroy(); | tiles_->current_tile.gpu_resources_destroy(); | |||||||||||
| gpu_context_disable(); | ||||||||||||
| return false; | ||||||||||||
| } | ||||||||||||
| if (!tiles_->current_tile.gl_resources_ensure()) { | ||||||||||||
| tiles_->current_tile.gl_resources_destroy(); | ||||||||||||
| gpu_context_disable(); | gpu_context_disable(); | |||||||||||
| return false; | return false; | |||||||||||
| } | } | |||||||||||
| /* Update texture dimensions if needed. */ | /* Update texture dimensions if needed. */ | |||||||||||
| if (current_tile.texture.width != texture_width || | current_tile.texture.ensure_size(texture_width, texture_height); | |||||||||||
| current_tile.texture.height != texture_height) { | ||||||||||||
| glActiveTexture(GL_TEXTURE0); | ||||||||||||
| 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); | ||||||||||||
| current_tile.texture.width = texture_width; | ||||||||||||
| current_tile.texture.height = texture_height; | ||||||||||||
| glBindTexture(GL_TEXTURE_2D, 0); | ||||||||||||
| } | ||||||||||||
| /* Update PBO dimensions if needed. | /* Update PBO dimensions if needed. | |||||||||||
| * | * | |||||||||||
| * NOTE: Allocate the PBO for the size which will fit the final render resolution (as in, | * NOTE: Allocate the PBO for 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 | * 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. | * 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 | * 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. */ | * 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 | /* TODO(sergey): Investigate whether keeping the PBO exact size of the texture makes non-interop | |||||||||||
| * mode faster. */ | * mode faster. */ | |||||||||||
| const int buffer_width = params.size.x; | const int buffer_width = params.size.x; | |||||||||||
| const int buffer_height = params.size.y; | const int buffer_height = params.size.y; | |||||||||||
| if (current_tile_buffer_object.width != buffer_width || | current_tile_buffer_object.ensure_size(buffer_width, buffer_height); | |||||||||||
| current_tile_buffer_object.height != buffer_height) { | ||||||||||||
| const size_t size_in_bytes = sizeof(half4) * buffer_width * buffer_height; | ||||||||||||
| 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); | ||||||||||||
| current_tile_buffer_object.width = buffer_width; | ||||||||||||
| current_tile_buffer_object.height = buffer_height; | ||||||||||||
| } | ||||||||||||
| /* Store an updated parameters of the current tile. | /* 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 | * 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. */ | * the easiest and is not expensive. */ | |||||||||||
| tiles_->current_tile.tile.params = params; | tiles_->current_tile.tile.params = params; | |||||||||||
| return true; | return true; | |||||||||||
| } | } | |||||||||||
| static void update_tile_texture_pixels(const DrawTileAndPBO &tile) | static void update_tile_texture_pixels(const DrawTileAndPBO &tile) | |||||||||||
| { | { | |||||||||||
| const GLTexture &texture = tile.tile.texture; | const DisplayGPUTexture &texture = tile.tile.texture; | |||||||||||
| DCHECK_NE(tile.buffer_object.gl_id, 0); | if (!DCHECK_NOTNULL(tile.buffer_object.gpu_pixel_buffer)) { | |||||||||||
| LOG(ERROR) << "Display driver tile pixel buffer unavailable."; | ||||||||||||
| glActiveTexture(GL_TEXTURE0); | return; | |||||||||||
| glBindTexture(GL_TEXTURE_2D, texture.gl_id); | } | |||||||||||
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, tile.buffer_object.gl_id); | GPU_texture_update_sub_from_pixel_buffer(texture.gpu_texture, | |||||||||||
| GPU_DATA_HALF_FLOAT, | ||||||||||||
| glTexSubImage2D( | tile.buffer_object.gpu_pixel_buffer, | |||||||||||
| GL_TEXTURE_2D, 0, 0, 0, texture.width, texture.height, GL_RGBA, GL_HALF_FLOAT, 0); | 0, | |||||||||||
| 0, | ||||||||||||
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); | 0, | |||||||||||
| glBindTexture(GL_TEXTURE_2D, 0); | texture.width, | |||||||||||
| texture.height, | ||||||||||||
| 0); | ||||||||||||
Not Done Inline ActionsUse DCHECK_NOTNULL/CHECK_NOTNULL check that something is not null. sergey: Use `DCHECK_NOTNULL`/`CHECK_NOTNULL` check that something is not null. | ||||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::update_end() | void BlenderDisplayDriver::update_end() | |||||||||||
| { | { | |||||||||||
| /* Unpack the PBO into the texture as soon as the new content is provided. | /* 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 | * 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 | * lifetime is outside of control of the display driver) are still valid, as well as allows to | |||||||||||
| Show All 10 Lines | void BlenderDisplayDriver::update_end() | |||||||||||
| if (!background_ && | if (!background_ && | |||||||||||
| GPU_type_matches_ex(GPU_DEVICE_NVIDIA, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_ANY)) { | GPU_type_matches_ex(GPU_DEVICE_NVIDIA, GPU_OS_MAC, GPU_DRIVER_ANY, GPU_BACKEND_ANY)) { | |||||||||||
| tiles_->current_tile.need_update_texture_pixels = true; | tiles_->current_tile.need_update_texture_pixels = true; | |||||||||||
| } | } | |||||||||||
| else { | else { | |||||||||||
| update_tile_texture_pixels(tiles_->current_tile); | update_tile_texture_pixels(tiles_->current_tile); | |||||||||||
| } | } | |||||||||||
| gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | /* Ensure GPU fence exists to synchronize upload. */ | |||||||||||
| glFlush(); | GPU_fence_signal(gpu_upload_sync_); | |||||||||||
| GPU_flush(); | ||||||||||||
Not Done Inline ActionsWould it make sense, or even possible, to allocate fences as part of the rest of the GPU resources (as opposite of doing on-demand) ? sergey: Would it make sense, or even possible, to allocate fences as part of the rest of the GPU… | ||||||||||||
| gpu_context_disable(); | gpu_context_disable(); | |||||||||||
| } | } | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * Texture buffer mapping. | * Texture buffer mapping. | |||||||||||
| */ | */ | |||||||||||
| half4 *BlenderDisplayDriver::map_texture_buffer() | half4 *BlenderDisplayDriver::map_texture_buffer() | |||||||||||
| { | { | |||||||||||
| const uint pbo_gl_id = tiles_->current_tile.buffer_object.gl_id; | GPUPixelBuffer *pix_buf = tiles_->current_tile.buffer_object.gpu_pixel_buffer; | |||||||||||
| if (!DCHECK_NOTNULL(pix_buf)) { | ||||||||||||
| DCHECK_NE(pbo_gl_id, 0); | LOG(ERROR) << "Display driver tile pixel buffer unavailable."; | |||||||||||
| return nullptr; | ||||||||||||
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_gl_id); | } | |||||||||||
| half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>(GPU_pixel_buffer_map(pix_buf)); | ||||||||||||
| half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>( | ||||||||||||
| glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); | ||||||||||||
| if (!mapped_rgba_pixels) { | if (!mapped_rgba_pixels) { | |||||||||||
| LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object."; | LOG(ERROR) << "Error mapping BlenderDisplayDriver pixel buffer object."; | |||||||||||
| } | } | |||||||||||
| return mapped_rgba_pixels; | return mapped_rgba_pixels; | |||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::unmap_texture_buffer() | void BlenderDisplayDriver::unmap_texture_buffer() | |||||||||||
| { | { | |||||||||||
| glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); | GPUPixelBuffer *pix_buf = tiles_->current_tile.buffer_object.gpu_pixel_buffer; | |||||||||||
| if (!DCHECK_NOTNULL(pix_buf)) { | ||||||||||||
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); | LOG(ERROR) << "Display driver tile pixel buffer unavailable."; | |||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| GPU_pixel_buffer_unmap(pix_buf); | ||||||||||||
| } | } | |||||||||||
| /* -------------------------------------------------------------------- | /* -------------------------------------------------------------------- | |||||||||||
| * Graphics interoperability. | * Graphics interoperability. | |||||||||||
| */ | */ | |||||||||||
| BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get() | BlenderDisplayDriver::GraphicsInterop BlenderDisplayDriver::graphics_interop_get() | |||||||||||
| { | { | |||||||||||
| GraphicsInterop interop_dst; | GraphicsInterop interop_dst; | |||||||||||
| interop_dst.buffer_width = tiles_->current_tile.buffer_object.width; | interop_dst.buffer_width = tiles_->current_tile.buffer_object.width; | |||||||||||
| interop_dst.buffer_height = tiles_->current_tile.buffer_object.height; | interop_dst.buffer_height = tiles_->current_tile.buffer_object.height; | |||||||||||
| interop_dst.opengl_pbo_id = tiles_->current_tile.buffer_object.gl_id; | interop_dst.opengl_pbo_id = (int)GPU_pixel_buffer_get_native_handle( | |||||||||||
| tiles_->current_tile.buffer_object.gpu_pixel_buffer); | ||||||||||||
| return interop_dst; | return interop_dst; | |||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::graphics_interop_activate() | void BlenderDisplayDriver::graphics_interop_activate() | |||||||||||
| { | { | |||||||||||
| gpu_context_enable(); | gpu_context_enable(); | |||||||||||
| } | } | |||||||||||
| Show All 16 Lines | ||||||||||||
| { | { | |||||||||||
| zoom_ = make_float2(zoom_x, zoom_y); | zoom_ = make_float2(zoom_x, zoom_y); | |||||||||||
| } | } | |||||||||||
| /* Update vertex buffer with new coordinates of vertex positions and texture coordinates. | /* Update vertex buffer with new coordinates of vertex positions and texture coordinates. | |||||||||||
| * This buffer is used to render texture in the viewport. | * This buffer is used to render texture in the viewport. | |||||||||||
| * | * | |||||||||||
| * NOTE: The buffer needs to be bound. */ | * NOTE: The buffer needs to be bound. */ | |||||||||||
| static void vertex_buffer_update(const DisplayDriver::Params ¶ms) | static void vertex_draw(const DisplayDriver::Params ¶ms, | |||||||||||
| int texcoord_attribute, | ||||||||||||
| int position_attribute) | ||||||||||||
| { | { | |||||||||||
| const int x = params.full_offset.x; | const int x = params.full_offset.x; | |||||||||||
| const int y = params.full_offset.y; | const int y = params.full_offset.y; | |||||||||||
| const int width = params.size.x; | const int width = params.size.x; | |||||||||||
| const int height = params.size.y; | const int height = params.size.y; | |||||||||||
| /* Invalidate old contents - avoids stalling if the buffer is still waiting in queue to be | immBegin(GPU_PRIM_TRI_STRIP, 4); | |||||||||||
Not Done Inline ActionsWhy was this changed to the immediate mode emulation? sergey: Why was this changed to the immediate mode emulation? | ||||||||||||
Not Done Inline ActionsThis was done for simplicity of the code, as it would seem the cost overhead of performing GPUBatch rendering with a pre-baked vertex buffer is more expensive than using GPUImmediate for very small vertex counts, especially if they are subject to data changing relatively frequently. However, please let me know if you prefer this to be written using GPUBatch + GPUVertBuf, as this can be changed. MichaelPW: This was done for simplicity of the code, as it would seem the cost overhead of performing… | ||||||||||||
Not Done Inline ActionsThanks for the explanation. If there is no speed penalty on both OpenGL and Metal, and the GPU_immediate.h is not planned to be deprecated with the coming development in the GPU module your suggested code is indeed simpler. sergey: Thanks for the explanation. If there is no speed penalty on both OpenGL and Metal, and the… | ||||||||||||
| * rendered. */ | ||||||||||||
| glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), NULL, GL_STREAM_DRAW); | ||||||||||||
| float *vpointer = reinterpret_cast<float *>(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); | immAttr2f(texcoord_attribute, 1.0f, 0.0f); | |||||||||||
| if (!vpointer) { | immVertex2f(position_attribute, x + width, y); | |||||||||||
| return; | ||||||||||||
| } | immAttr2f(texcoord_attribute, 1.0f, 1.0f); | |||||||||||
| immVertex2f(position_attribute, x + width, y + height); | ||||||||||||
| immAttr2f(texcoord_attribute, 0.0f, 0.0f); | ||||||||||||
| immVertex2f(position_attribute, x, y); | ||||||||||||
| vpointer[0] = 0.0f; | immAttr2f(texcoord_attribute, 0.0f, 1.0f); | |||||||||||
| vpointer[1] = 0.0f; | immVertex2f(position_attribute, x, y + height); | |||||||||||
| 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); | immEnd(); | |||||||||||
| } | } | |||||||||||
| static void draw_tile(const float2 &zoom, | static void draw_tile(const float2 &zoom, | |||||||||||
| const int texcoord_attribute, | const int texcoord_attribute, | |||||||||||
| const int position_attribute, | const int position_attribute, | |||||||||||
| const DrawTile &draw_tile, | const DrawTile &draw_tile) | |||||||||||
| const uint gl_vertex_buffer) | ||||||||||||
| { | { | |||||||||||
| if (!draw_tile.ready_to_draw()) { | if (!draw_tile.ready_to_draw()) { | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| const GLTexture &texture = draw_tile.texture; | const DisplayGPUTexture &texture = draw_tile.texture; | |||||||||||
| DCHECK_NE(texture.gl_id, 0); | if (!DCHECK_NOTNULL(texture.gpu_texture)) { | |||||||||||
| DCHECK_NE(gl_vertex_buffer, 0); | LOG(ERROR) << "Display driver tile GPU texture resource unavailable."; | |||||||||||
| return; | ||||||||||||
| glBindBuffer(GL_ARRAY_BUFFER, gl_vertex_buffer); | } | |||||||||||
| /* 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); | GPU_texture_bind(texture.gpu_texture, 0); | |||||||||||
| /* Trick to keep sharp rendering without jagged edges on all GPUs. | /* 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 | * The idea here is to enforce driver to use linear interpolation when the image is not zoomed | |||||||||||
| * in. | * in. | |||||||||||
| * For the render result with a resolution divider in effect we always use nearest interpolation. | * 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 | * 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. */ | * the zoom level 1. The MAG filter is always NEAREST. */ | |||||||||||
| const float zoomed_width = draw_tile.params.size.x * zoom.x; | const float zoomed_width = draw_tile.params.size.x * zoom.x; | |||||||||||
| const float zoomed_height = draw_tile.params.size.y * zoom.y; | 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) { | if (texture.width != draw_tile.params.size.x || texture.height != draw_tile.params.size.y) { | |||||||||||
| /* Resolution divider is different from 1, force nearest interpolation. */ | /* Resolution divider is different from 1, force nearest interpolation. */ | |||||||||||
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | GPU_texture_filter_mode(texture.gpu_texture, false); | |||||||||||
| } | } | |||||||||||
| else if (zoomed_width - draw_tile.params.size.x > 0.5f || | else if (zoomed_width - draw_tile.params.size.x > 0.5f || | |||||||||||
| zoomed_height - draw_tile.params.size.y > 0.5f) { | zoomed_height - draw_tile.params.size.y > 0.5f) { | |||||||||||
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | GPU_texture_filter_mode(texture.gpu_texture, false); | |||||||||||
| } | } | |||||||||||
| else { | else { | |||||||||||
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | GPU_texture_filter_mode(texture.gpu_texture, true); | |||||||||||
| } | } | |||||||||||
| glVertexAttribPointer( | /* Draw at the parameters for which the texture has been updated for. This allows to always draw | |||||||||||
| texcoord_attribute, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const GLvoid *)0); | * texture during bordered-rendered camera view without flickering. The validness of the display | |||||||||||
| glVertexAttribPointer(position_attribute, | * parameters for a texture is guaranteed by the initial "clear" state which makes drawing to | |||||||||||
| 2, | * have an early output. | |||||||||||
| GL_FLOAT, | * | |||||||||||
| GL_FALSE, | * Such approach can cause some extra "jelly" effect during panning, but it is not more jelly | |||||||||||
| 4 * sizeof(float), | * than overlay of selected objects. Also, it's possible to redraw texture at an intersection of | |||||||||||
| (const GLvoid *)(sizeof(float) * 2)); | * the texture draw parameters and the latest updated draw parameters (although, complexity of | |||||||||||
| * doing it might not worth it. */ | ||||||||||||
| glDrawArrays(GL_TRIANGLE_FAN, 0, 4); | vertex_draw(draw_tile.params, texcoord_attribute, position_attribute); | |||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::flush() | void BlenderDisplayDriver::flush() | |||||||||||
| { | { | |||||||||||
| /* This is called from the render thread that also calls update_begin/end, right before ending | /* This is called from the render thread that also calls update_begin/end, right before ending | |||||||||||
| * the render loop. We wait for any queued PBO and render commands to be done, before destroying | * the render loop. We wait for any queued PBO and render commands to be done, before destroying | |||||||||||
| * the render thread and activating the context in the main thread to destroy resources. | * the render thread and activating the context in the main thread to destroy resources. | |||||||||||
| * | * | |||||||||||
| * If we don't do this, the NVIDIA driver hangs for a few seconds for when ending 3D viewport | * If we don't do this, the NVIDIA driver hangs for a few seconds for when ending 3D viewport | |||||||||||
| * rendering, for unknown reasons. This was found with NVIDIA driver version 470.73 and a Quadro | * rendering, for unknown reasons. This was found with NVIDIA driver version 470.73 and a Quadro | |||||||||||
| * RTX 6000 on Linux. */ | * RTX 6000 on Linux. */ | |||||||||||
| if (!gpu_context_enable()) { | if (!gpu_context_enable()) { | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| if (gl_upload_sync_) { | GPU_fence_wait(gpu_upload_sync_); | |||||||||||
| glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); | GPU_fence_wait(gpu_render_sync_); | |||||||||||
| } | ||||||||||||
| if (gl_render_sync_) { | ||||||||||||
| glWaitSync((GLsync)gl_render_sync_, 0, GL_TIMEOUT_IGNORED); | ||||||||||||
| } | ||||||||||||
| gpu_context_disable(); | gpu_context_disable(); | |||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::draw(const Params ¶ms) | void BlenderDisplayDriver::draw(const Params ¶ms) | |||||||||||
| { | { | |||||||||||
| gpu_context_lock(); | gpu_context_lock(); | |||||||||||
| if (need_clear_) { | if (need_clear_) { | |||||||||||
| /* Texture is requested to be cleared and was not yet cleared. | /* Texture is requested to be cleared and was not yet cleared. | |||||||||||
| * | * | |||||||||||
| * Do early return which should be equivalent of drawing all-zero texture. | * 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 | * Watch out for the lock though so that the clear happening during update is properly | |||||||||||
| * synchronized here. */ | * synchronized here. */ | |||||||||||
| gpu_context_unlock(); | gpu_context_unlock(); | |||||||||||
| return; | return; | |||||||||||
| } | } | |||||||||||
| if (gl_upload_sync_) { | GPU_fence_wait(gpu_upload_sync_); | |||||||||||
| glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); | GPU_blend(GPU_BLEND_ALPHA_PREMULT); | |||||||||||
| } | ||||||||||||
| 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 contexts. | ||||||||||||
| * 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); | GPUShader *active_shader = display_shader_->bind(params.full_size.x, params.full_size.y); | |||||||||||
| const int texcoord_attribute = display_shader_->get_tex_coord_attrib_location(); | GPUVertFormat *format = immVertexFormat(); | |||||||||||
| const int position_attribute = display_shader_->get_position_attrib_location(); | const int texcoord_attribute = GPU_vertformat_attr_add( | |||||||||||
| format, display_shader_->tex_coord_attribute_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT); | ||||||||||||
| const int position_attribute = GPU_vertformat_attr_add( | ||||||||||||
| format, display_shader_->position_attribute_name, GPU_COMP_F32, 2, GPU_FETCH_FLOAT); | ||||||||||||
| /* Note: Shader is bound again through IMM to register this shader with the imm module | ||||||||||||
| * and perform required setup for IMM rendering. This is required as the IMM module | ||||||||||||
| * needs to be aware of which shader is bound, and the main display shader | ||||||||||||
| * is bound externally. */ | ||||||||||||
| immBindShader(active_shader); | ||||||||||||
| glEnableVertexAttribArray(texcoord_attribute); | draw_tile(zoom_, texcoord_attribute, position_attribute, tiles_->current_tile.tile); | |||||||||||
| glEnableVertexAttribArray(position_attribute); | ||||||||||||
| if (tiles_->current_tile.need_update_texture_pixels) { | ||||||||||||
| update_tile_texture_pixels(tiles_->current_tile); | ||||||||||||
| tiles_->current_tile.need_update_texture_pixels = false; | ||||||||||||
| } | ||||||||||||
| draw_tile(zoom_, | ||||||||||||
| texcoord_attribute, | ||||||||||||
| position_attribute, | ||||||||||||
| tiles_->current_tile.tile, | ||||||||||||
| tiles_->gl_vertex_buffer); | ||||||||||||
| for (const DrawTile &tile : tiles_->finished_tiles.tiles) { | for (const DrawTile &tile : tiles_->finished_tiles.tiles) { | |||||||||||
| draw_tile(zoom_, texcoord_attribute, position_attribute, tile, tiles_->gl_vertex_buffer); | draw_tile(zoom_, texcoord_attribute, position_attribute, tile); | |||||||||||
| } | } | |||||||||||
| display_shader_->unbind(); | /* Reset IMM shader bind state. */ | |||||||||||
| immUnbindProgram(); | ||||||||||||
| glBindTexture(GL_TEXTURE_2D, 0); | display_shader_->unbind(); | |||||||||||
| glBindVertexArray(0); | ||||||||||||
| glBindBuffer(GL_ARRAY_BUFFER, 0); | ||||||||||||
| glDeleteVertexArrays(1, &vertex_array_object); | ||||||||||||
| glDisable(GL_BLEND); | GPU_blend(GPU_BLEND_NONE); | |||||||||||
| gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | GPU_fence_signal(gpu_render_sync_); | |||||||||||
| glFlush(); | GPU_flush(); | |||||||||||
| gpu_context_unlock(); | gpu_context_unlock(); | |||||||||||
| VLOG_DEVICE_STATS << "Display driver number of textures: " << GLTexture::num_used; | VLOG_DEVICE_STATS << "Display driver number of textures: " << DisplayGPUTexture::num_used; | |||||||||||
| VLOG_DEVICE_STATS << "Display driver number of PBOs: " << GLPixelBufferObject::num_used; | VLOG_DEVICE_STATS << "Display driver number of PBOs: " << DisplayGPUPixelBuffer::num_used; | |||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::gpu_context_create() | void BlenderDisplayDriver::gpu_context_create() | |||||||||||
| { | { | |||||||||||
| if (!RE_engine_gpu_context_create(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data))) { | if (!RE_engine_gpu_context_create(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data))) { | |||||||||||
| LOG(ERROR) << "Error creating OpenGL context."; | LOG(ERROR) << "Error creating GPU context."; | |||||||||||
| return; | ||||||||||||
| } | ||||||||||||
| /* Create global GPU resources for display driver. */ | ||||||||||||
| if (!gpu_resources_create()) { | ||||||||||||
| LOG(ERROR) << "Error creating GPU resources for Cycles Display Driver."; | ||||||||||||
| return; | ||||||||||||
| } | } | |||||||||||
| } | } | |||||||||||
| bool BlenderDisplayDriver::gpu_context_enable() | bool BlenderDisplayDriver::gpu_context_enable() | |||||||||||
| { | { | |||||||||||
| return RE_engine_gpu_context_enable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); | return RE_engine_gpu_context_enable(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); | |||||||||||
| } | } | |||||||||||
| Show All 12 Lines | void BlenderDisplayDriver::gpu_context_lock() | |||||||||||
| RE_engine_gpu_context_lock(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); | RE_engine_gpu_context_lock(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); | |||||||||||
| } | } | |||||||||||
| void BlenderDisplayDriver::gpu_context_unlock() | void BlenderDisplayDriver::gpu_context_unlock() | |||||||||||
| { | { | |||||||||||
| RE_engine_gpu_context_unlock(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); | RE_engine_gpu_context_unlock(reinterpret_cast<RenderEngine *>(b_engine_.ptr.data)); | |||||||||||
| } | } | |||||||||||
| bool BlenderDisplayDriver::gpu_resources_create() | ||||||||||||
| { | ||||||||||||
| /* Ensure context is active for resource creation. */ | ||||||||||||
| if (!gpu_context_enable()) { | ||||||||||||
| LOG(ERROR) << "Error enabling GPU context."; | ||||||||||||
| return false; | ||||||||||||
| } | ||||||||||||
| gpu_upload_sync_ = GPU_fence_create(); | ||||||||||||
| gpu_render_sync_ = GPU_fence_create(); | ||||||||||||
Not Done Inline ActionsAdd an assert, or check for nullptr and return false? Also, if the sync objects are created here, checks before sync calls can be removed in the file? sergey: Add an assert, or check for nullptr and return false?
Also, if the sync objects are created… | ||||||||||||
| if (!DCHECK_NOTNULL(gpu_upload_sync_) || !DCHECK_NOTNULL(gpu_render_sync_)) { | ||||||||||||
| LOG(ERROR) << "Error creating GPU synchronization primtiives."; | ||||||||||||
| assert(0); | ||||||||||||
| return false; | ||||||||||||
| } | ||||||||||||
| gpu_context_disable(); | ||||||||||||
| return true; | ||||||||||||
| } | ||||||||||||
| void BlenderDisplayDriver::gpu_resources_destroy() | void BlenderDisplayDriver::gpu_resources_destroy() | |||||||||||
| { | { | |||||||||||
| gpu_context_enable(); | gpu_context_enable(); | |||||||||||
| tiles_->current_tile.gl_resources_destroy(); | tiles_->current_tile.gpu_resources_destroy(); | |||||||||||
| tiles_->finished_tiles.gl_resources_destroy_and_clear(); | tiles_->finished_tiles.gl_resources_destroy_and_clear(); | |||||||||||
| tiles_->gl_resources_destroy(); | ||||||||||||
| /* Fences. */ | ||||||||||||
| if (gpu_render_sync_) { | ||||||||||||
| GPU_fence_free(gpu_render_sync_); | ||||||||||||
| gpu_render_sync_ = nullptr; | ||||||||||||
| } | ||||||||||||
| if (gpu_upload_sync_) { | ||||||||||||
| GPU_fence_free(gpu_upload_sync_); | ||||||||||||
| gpu_upload_sync_ = nullptr; | ||||||||||||
| } | ||||||||||||
| gpu_context_disable(); | gpu_context_disable(); | |||||||||||
| gpu_context_destroy(); | gpu_context_destroy(); | |||||||||||
| } | } | |||||||||||
| CCL_NAMESPACE_END | CCL_NAMESPACE_END | |||||||||||
Compilation errors are logged to console ?