diff options
Diffstat (limited to 'intern/cycles')
32 files changed, 1324 insertions, 430 deletions
diff --git a/intern/cycles/app/CMakeLists.txt b/intern/cycles/app/CMakeLists.txt index 75ff7114dd7..3248ef0dcda 100644 --- a/intern/cycles/app/CMakeLists.txt +++ b/intern/cycles/app/CMakeLists.txt @@ -33,15 +33,19 @@ else() endif() if(WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI) - list(APPEND LIBRARIES ${GLUT_LIBRARIES}) + add_definitions(${GL_DEFINITIONS}) + list(APPEND INC_SYS + ${GLEW_INCLUDE_DIR} + ${SDL2_INCLUDE_DIRS} + ) + list(APPEND LIBRARIES + ${CYCLES_GL_LIBRARIES} + ${SDL2_LIBRARIES} + ) endif() -list(APPEND LIBRARIES ${CYCLES_GL_LIBRARIES}) - # Common configuration. -add_definitions(${GL_DEFINITIONS}) - include_directories(${INC}) include_directories(SYSTEM ${INC_SYS}) @@ -55,6 +59,18 @@ if(WITH_CYCLES_STANDALONE) oiio_output_driver.cpp oiio_output_driver.h ) + + if(WITH_CYCLES_STANDALONE_GUI) + list(APPEND SRC + opengl/display_driver.cpp + opengl/display_driver.h + opengl/shader.cpp + opengl/shader.h + opengl/window.cpp + opengl/window.h + ) + endif() + add_executable(cycles ${SRC} ${INC} ${INC_SYS}) unset(SRC) @@ -69,6 +85,10 @@ if(WITH_CYCLES_STANDALONE) # OpenImageDenoise uses BNNS from the Accelerate framework. set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS " -framework Accelerate") endif() + if(WITH_CYCLES_STANDALONE_GUI) + set_property(TARGET cycles APPEND_STRING PROPERTY LINK_FLAGS + " -framework Cocoa -framework CoreAudio -framework AudioUnit -framework AudioToolbox -framework ForceFeedback -framework CoreVideo") + endif() endif() if(UNIX AND NOT APPLE) diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp index 0e425ac3d8f..ef20f64debd 100644 --- a/intern/cycles/app/cycles_standalone.cpp +++ b/intern/cycles/app/cycles_standalone.cpp @@ -27,11 +27,10 @@ #include "app/oiio_output_driver.h" #ifdef WITH_CYCLES_STANDALONE_GUI -# include "util/view.h" +# include "opengl/display_driver.h" +# include "opengl/window.h" #endif -#include "app/cycles_xml.h" - CCL_NAMESPACE_BEGIN struct Options { @@ -117,7 +116,14 @@ static void session_init() options.output_pass = "combined"; options.session = new Session(options.session_params, options.scene_params); - if (!options.output_filepath.empty()) { +#ifdef WITH_CYCLES_STANDALONE_GUI + if (!options.session_params.background) { + options.session->set_display_driver(make_unique<OpenGLDisplayDriver>( + window_opengl_context_enable, window_opengl_context_disable)); + } + else +#endif + if (!options.output_filepath.empty()) { options.session->set_output_driver(make_unique<OIIOOutputDriver>( options.output_filepath, options.output_pass, session_print)); } @@ -126,7 +132,7 @@ static void session_init() options.session->progress.set_update_callback(function_bind(&session_print_status)); #ifdef WITH_CYCLES_STANDALONE_GUI else - options.session->progress.set_update_callback(function_bind(&view_redraw)); + options.session->progress.set_update_callback(function_bind(&window_redraw)); #endif /* load scene */ @@ -191,10 +197,10 @@ static void display_info(Progress &progress) sample_time, interactive.c_str()); - view_display_info(str.c_str()); + window_display_info(str.c_str()); if (options.show_help) - view_display_help(); + window_display_help(); } static void display() @@ -525,15 +531,15 @@ int main(int argc, const char **argv) string title = "Cycles: " + path_filename(options.filepath); /* init/exit are callback so they run while GL is initialized */ - view_main_loop(title.c_str(), - options.width, - options.height, - session_init, - session_exit, - resize, - display, - keyboard, - motion); + window_main_loop(title.c_str(), + options.width, + options.height, + session_init, + session_exit, + resize, + display, + keyboard, + motion); } #endif diff --git a/intern/cycles/app/opengl/display_driver.cpp b/intern/cycles/app/opengl/display_driver.cpp new file mode 100644 index 00000000000..8b99f3b6feb --- /dev/null +++ b/intern/cycles/app/opengl/display_driver.cpp @@ -0,0 +1,385 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#include "app/opengl/display_driver.h" +#include "app/opengl/shader.h" + +#include "util/log.h" +#include "util/string.h" + +#include <GL/glew.h> +#include <SDL.h> + +CCL_NAMESPACE_BEGIN + +/* -------------------------------------------------------------------- + * OpenGLDisplayDriver. + */ + +OpenGLDisplayDriver::OpenGLDisplayDriver(const function<bool()> &gl_context_enable, + const function<void()> &gl_context_disable) + : gl_context_enable_(gl_context_enable), gl_context_disable_(gl_context_disable) +{ +} + +OpenGLDisplayDriver::~OpenGLDisplayDriver() +{ +} + +/* -------------------------------------------------------------------- + * Update procedure. + */ + +void OpenGLDisplayDriver::next_tile_begin() +{ + /* Assuming no tiles used in interactive display. */ +} + +bool OpenGLDisplayDriver::update_begin(const Params ¶ms, int texture_width, int texture_height) +{ + /* Note that it's the responsibility of OpenGLDisplayDriver 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 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. */ + 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 OpenGLDisplayDriver::update_end() +{ + gl_upload_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + + gl_context_disable_(); +} + +/* -------------------------------------------------------------------- + * Texture buffer mapping. + */ + +half4 *OpenGLDisplayDriver::map_texture_buffer() +{ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture_.gl_pbo_id); + + half4 *mapped_rgba_pixels = reinterpret_cast<half4 *>( + glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY)); + if (!mapped_rgba_pixels) { + LOG(ERROR) << "Error mapping OpenGLDisplayDriver pixel buffer object."; + } + + if (texture_.need_clear) { + const int64_t texture_width = texture_.width; + const int64_t texture_height = texture_.height; + memset(reinterpret_cast<void *>(mapped_rgba_pixels), + 0, + texture_width * texture_height * sizeof(half4)); + texture_.need_clear = false; + } + + return mapped_rgba_pixels; +} + +void OpenGLDisplayDriver::unmap_texture_buffer() +{ + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +/* -------------------------------------------------------------------- + * Graphics interoperability. + */ + +OpenGLDisplayDriver::GraphicsInterop OpenGLDisplayDriver::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 OpenGLDisplayDriver::graphics_interop_activate() +{ + gl_context_enable_(); +} + +void OpenGLDisplayDriver::graphics_interop_deactivate() +{ + gl_context_disable_(); +} + +/* -------------------------------------------------------------------- + * Drawing. + */ + +void OpenGLDisplayDriver::clear() +{ + texture_.need_clear = true; +} + +void OpenGLDisplayDriver::draw(const Params ¶ms) +{ + /* See do_update_begin() for why no locking is required here. */ + 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. */ + return; + } + + if (!gl_draw_resources_ensure()) { + return; + } + + if (gl_upload_sync_) { + glWaitSync((GLsync)gl_upload_sync_, 0, GL_TIMEOUT_IGNORED); + } + + 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); + + 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 { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); + + texture_update_if_needed(); + vertex_buffer_update(params); + + 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(); + + glDisable(GL_BLEND); + + gl_render_sync_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); +} + +bool OpenGLDisplayDriver::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 OpenGLDisplayDriver::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_(); +} + +bool OpenGLDisplayDriver::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 OpenGLDisplayDriver::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 OpenGLDisplayDriver::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<float *>(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/app/opengl/display_driver.h b/intern/cycles/app/opengl/display_driver.h new file mode 100644 index 00000000000..92578412d68 --- /dev/null +++ b/intern/cycles/app/opengl/display_driver.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#pragma once + +#include <atomic> + +#include "app/opengl/shader.h" + +#include "session/display_driver.h" + +#include "util/function.h" +#include "util/unique_ptr.h" + +CCL_NAMESPACE_BEGIN + +class OpenGLDisplayDriver : public DisplayDriver { + public: + /* Callbacks for enabling and disabling the OpenGL context. Must be provided to support enabling + * the context on the Cycles render thread independent of the main thread. */ + OpenGLDisplayDriver(const function<bool()> &gl_context_enable, + const function<void()> &gl_context_disable); + ~OpenGLDisplayDriver(); + + 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 void next_tile_begin() override; + + 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; + + /* 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); + + /* Texture which contains pixels of the render result. */ + struct { + /* Indicates whether texture creation was attempted and succeeded. + * Used to avoid multiple attempts of texture creation on GPU issues or GPU context + * misconfiguration. */ + bool creation_attempted = false; + bool is_created = false; + + /* OpenGL resource IDs of the texture itself and Pixel Buffer Object (PBO) used to write + * pixels to it. + * + * NOTE: Allocated on the engine's context. */ + uint gl_id = 0; + uint gl_pbo_id = 0; + + /* Is true when new data was written to the PBO, meaning, the texture might need to be resized + * and new data is to be uploaded to the GPU. */ + bool need_update = false; + + /* Content of the texture is to be filled with zeroes. */ + std::atomic<bool> need_clear = true; + + /* Dimensions of the texture in pixels. */ + int width = 0; + int height = 0; + + /* Dimensions of the underlying PBO. */ + int buffer_width = 0; + int buffer_height = 0; + } texture_; + + OpenGLShader 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); + + function<bool()> gl_context_enable_ = nullptr; + function<void()> gl_context_disable_ = nullptr; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/shader.cpp b/intern/cycles/app/opengl/shader.cpp new file mode 100644 index 00000000000..9db9ea7fce9 --- /dev/null +++ b/intern/cycles/app/opengl/shader.cpp @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#include "app/opengl/shader.h" + +#include "util/log.h" +#include "util/string.h" + +#include <GL/glew.h> + +CCL_NAMESPACE_BEGIN + +/* -------------------------------------------------------------------- + * OpenGLShader. + */ + +static const char *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 *FRAGMENT_SHADER = + "#version 330\n" + "uniform sampler2D image_texture;\n" + "in vec2 texCoord_interp;\n" + "out vec4 fragColor;\n" + "\n" + "void main()\n" + "{\n" + " vec4 rgba = texture(image_texture, texCoord_interp);\n" + /* Harcoded Rec.709 gamma, should use OpenColorIO eventually. */ + " fragColor = pow(rgba, vec4(0.45, 0.45, 0.45, 1.0));\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_shader_program(void) +{ + const struct Shader { + const char *source; + const GLenum type; + } shaders[2] = {{VERTEX_SHADER, GL_VERTEX_SHADER}, {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); + + GLint link_status; + glGetProgramiv(program, GL_LINK_STATUS, &link_status); + if (!link_status) { + GLchar log[5000]; + GLsizei length = 0; + glGetShaderInfoLog(program, sizeof(log), &length, log); + shader_print_errors("linking", log, VERTEX_SHADER); + shader_print_errors("linking", log, FRAGMENT_SHADER); + return 0; + } + + return program; +} + +int OpenGLShader::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 OpenGLShader::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_; +} + +void OpenGLShader::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 OpenGLShader::unbind() +{ +} + +uint OpenGLShader::get_shader_program() +{ + return shader_program_; +} + +void OpenGLShader::create_shader_if_needed() +{ + if (shader_program_ || shader_compile_attempted_) { + return; + } + + shader_compile_attempted_ = true; + + shader_program_ = compile_shader_program(); + 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 OpenGLShader::destroy_shader() +{ + glDeleteProgram(shader_program_); + shader_program_ = 0; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/shader.h b/intern/cycles/app/opengl/shader.h new file mode 100644 index 00000000000..6ca121ca6ff --- /dev/null +++ b/intern/cycles/app/opengl/shader.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 OpenGL Foundation */ + +#pragma once + +#include "util/types.h" + +CCL_NAMESPACE_BEGIN + +class OpenGLShader { + public: + static constexpr const char *position_attribute_name = "pos"; + static constexpr const char *tex_coord_attribute_name = "texCoord"; + + OpenGLShader() = default; + virtual ~OpenGLShader() = default; + + /* Get attribute location for position and texture coordinate respectively. + * NOTE: The shader needs to be bound to have access to those. */ + int get_position_attrib_location(); + int get_tex_coord_attrib_location(); + + void bind(int width, int height); + void unbind(); + + protected: + uint get_shader_program(); + + void create_shader_if_needed(); + void destroy_shader(); + + /* Cached values of various OpenGL resources. */ + int position_attribute_location_ = -1; + int tex_coord_attribute_location_ = -1; + + 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; +}; + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/window.cpp b/intern/cycles/app/opengl/window.cpp new file mode 100644 index 00000000000..7351ae3eecd --- /dev/null +++ b/intern/cycles/app/opengl/window.cpp @@ -0,0 +1,352 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#include <stdio.h> +#include <stdlib.h> + +#include "app/opengl/window.h" + +#include "util/string.h" +#include "util/thread.h" +#include "util/time.h" +#include "util/version.h" + +#include <GL/glew.h> +#include <SDL.h> + +CCL_NAMESPACE_BEGIN + +/* structs */ + +struct Window { + WindowInitFunc initf = nullptr; + WindowExitFunc exitf = nullptr; + WindowResizeFunc resize = nullptr; + WindowDisplayFunc display = nullptr; + WindowKeyboardFunc keyboard = nullptr; + WindowMotionFunc motion = nullptr; + + bool first_display = true; + bool redraw = false; + + int mouseX = 0, mouseY = 0; + int mouseBut0 = 0, mouseBut2 = 0; + + int width = 0, height = 0; + + SDL_Window *window = nullptr; + SDL_GLContext gl_context = nullptr; + thread_mutex gl_context_mutex; +} V; + +/* public */ + +static void window_display_text(int x, int y, const char *text) +{ +/* Not currently supported, need to add text rendering support. */ +#if 0 + const char *c; + + glRasterPos3f(x, y, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + printf("display %s\n", text); + + for (c = text; *c != '\0'; c++) { + const uint8_t *bitmap = helvetica10_character_map[*c]; + glBitmap(bitmap[0], + helvetica10_height, + helvetica10_x_offset, + helvetica10_y_offset, + bitmap[0], + 0.0f, + bitmap + 1); + } +#else + static string last_text = ""; + + if (text != last_text) { + printf("%s\n", text); + last_text = text; + } +#endif +} + +void window_display_info(const char *info) +{ + const int height = 20; + +#if 0 + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.1f, 0.1f, 0.1f, 0.8f); + glRectf(0.0f, V.height - height, V.width, V.height); + glDisable(GL_BLEND); + + glColor3f(0.5f, 0.5f, 0.5f); +#endif + + window_display_text(10, 7 + V.height - height, info); + +#if 0 + glColor3f(1.0f, 1.0f, 1.0f); +#endif +} + +void window_display_help() +{ + const int w = (int)((float)V.width / 1.15f); + const int h = (int)((float)V.height / 1.15f); + + const int x1 = (V.width - w) / 2; +#if 0 + const int x2 = x1 + w; +#endif + + const int y1 = (V.height - h) / 2; + const int y2 = y1 + h; + +#if 0 + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(0.5f, 0.5f, 0.5f, 0.8f); + glRectf(x1, y1, x2, y2); + glDisable(GL_BLEND); + + glColor3f(0.8f, 0.8f, 0.8f); +#endif + + string info = string("Cycles Renderer ") + CYCLES_VERSION_STRING; + + window_display_text(x1 + 20, y2 - 20, info.c_str()); + window_display_text(x1 + 20, y2 - 40, "(C) 2011-2016 Blender Foundation"); + window_display_text(x1 + 20, y2 - 80, "Controls:"); + window_display_text(x1 + 20, y2 - 100, "h: Info/Help"); + window_display_text(x1 + 20, y2 - 120, "r: Reset"); + window_display_text(x1 + 20, y2 - 140, "p: Pause"); + window_display_text(x1 + 20, y2 - 160, "esc: Cancel"); + window_display_text(x1 + 20, y2 - 180, "q: Quit program"); + + window_display_text(x1 + 20, y2 - 210, "i: Interactive mode"); + window_display_text(x1 + 20, y2 - 230, "Left mouse: Move camera"); + window_display_text(x1 + 20, y2 - 250, "Right mouse: Rotate camera"); + window_display_text(x1 + 20, y2 - 270, "W/A/S/D: Move camera"); + window_display_text(x1 + 20, y2 - 290, "0/1/2/3: Set max bounces"); + +#if 0 + glColor3f(1.0f, 1.0f, 1.0f); +#endif +} + +static void window_display() +{ + if (V.first_display) { + if (V.initf) { + V.initf(); + } + if (V.exitf) { + atexit(V.exitf); + } + + V.first_display = false; + } + + window_opengl_context_enable(); + + glViewport(0, 0, V.width, V.height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearColor(0.05f, 0.05f, 0.05f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, V.width, 0, V.height, -1, 1); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glRasterPos3f(0, 0, 0); + + if (V.display) + V.display(); + + SDL_GL_SwapWindow(V.window); + window_opengl_context_disable(); +} + +static void window_reshape(int width, int height) +{ + if (V.width != width || V.height != height) { + if (V.resize) { + V.resize(width, height); + } + } + + V.width = width; + V.height = height; +} + +static bool window_keyboard(unsigned char key) +{ + if (V.keyboard) + V.keyboard(key); + + if (key == 'q') { + if (V.exitf) + V.exitf(); + return true; + } + + return false; +} + +static void window_mouse(int button, int state, int x, int y) +{ + if (button == SDL_BUTTON_LEFT) { + if (state == SDL_MOUSEBUTTONDOWN) { + V.mouseX = x; + V.mouseY = y; + V.mouseBut0 = 1; + } + else if (state == SDL_MOUSEBUTTONUP) { + V.mouseBut0 = 0; + } + } + else if (button == SDL_BUTTON_RIGHT) { + if (state == SDL_MOUSEBUTTONDOWN) { + V.mouseX = x; + V.mouseY = y; + V.mouseBut2 = 1; + } + else if (state == SDL_MOUSEBUTTONUP) { + V.mouseBut2 = 0; + } + } +} + +static void window_motion(int x, int y) +{ + const int but = V.mouseBut0 ? 0 : 2; + const int distX = x - V.mouseX; + const int distY = y - V.mouseY; + + if (V.motion) + V.motion(distX, distY, but); + + V.mouseX = x; + V.mouseY = y; +} + +bool window_opengl_context_enable() +{ + V.gl_context_mutex.lock(); + SDL_GL_MakeCurrent(V.window, V.gl_context); + return true; +} + +void window_opengl_context_disable() +{ + SDL_GL_MakeCurrent(V.window, nullptr); + V.gl_context_mutex.unlock(); +} + +void window_main_loop(const char *title, + int width, + int height, + WindowInitFunc initf, + WindowExitFunc exitf, + WindowResizeFunc resize, + WindowDisplayFunc display, + WindowKeyboardFunc keyboard, + WindowMotionFunc motion) +{ + V.width = width; + V.height = height; + V.first_display = true; + V.redraw = false; + V.initf = initf; + V.exitf = exitf; + V.resize = resize; + V.display = display; + V.keyboard = keyboard; + V.motion = motion; + + SDL_Init(SDL_INIT_VIDEO); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + V.window = SDL_CreateWindow(title, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, + height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); + if (V.window == nullptr) { + fprintf(stderr, "Failed to create window: %s\n", SDL_GetError()); + return; + } + + SDL_RaiseWindow(V.window); + + V.gl_context = SDL_GL_CreateContext(V.window); + glewInit(); + SDL_GL_MakeCurrent(V.window, nullptr); + + window_reshape(width, height); + window_display(); + + while (true) { + bool quit = false; + SDL_Event event; + while (!quit && SDL_PollEvent(&event)) { + if (event.type == SDL_TEXTINPUT) { + quit = window_keyboard(event.text.text[0]); + } + else if (event.type == SDL_MOUSEMOTION) { + window_motion(event.motion.x, event.motion.y); + } + else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { + window_mouse(event.button.button, event.button.state, event.button.x, event.button.y); + } + else if (event.type == SDL_WINDOWEVENT) { + if (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { + window_reshape(event.window.data1, event.window.data2); + } + } + else if (event.type == SDL_QUIT) { + if (V.exitf) { + V.exitf(); + } + quit = true; + } + } + + if (quit) { + break; + } + + if (V.redraw) { + V.redraw = false; + window_display(); + } + + SDL_WaitEventTimeout(NULL, 100); + } + + SDL_GL_DeleteContext(V.gl_context); + SDL_DestroyWindow(V.window); + SDL_Quit(); +} + +void window_redraw() +{ + V.redraw = true; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/app/opengl/window.h b/intern/cycles/app/opengl/window.h new file mode 100644 index 00000000000..531b5cab3fc --- /dev/null +++ b/intern/cycles/app/opengl/window.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +#pragma once + +/* Functions to display a simple OpenGL window using SDL, simplified to the + * bare minimum we need to reduce boilerplate code in tests apps. */ + +CCL_NAMESPACE_BEGIN + +typedef void (*WindowInitFunc)(); +typedef void (*WindowExitFunc)(); +typedef void (*WindowResizeFunc)(int width, int height); +typedef void (*WindowDisplayFunc)(); +typedef void (*WindowKeyboardFunc)(unsigned char key); +typedef void (*WindowMotionFunc)(int x, int y, int button); + +void window_main_loop(const char *title, + int width, + int height, + WindowInitFunc initf, + WindowExitFunc exitf, + WindowResizeFunc resize, + WindowDisplayFunc display, + WindowKeyboardFunc keyboard, + WindowMotionFunc motion); + +void window_display_info(const char *info); +void window_display_help(); +void window_redraw(); + +bool window_opengl_context_enable(); +void window_opengl_context_disable(); + +CCL_NAMESPACE_END diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py index 4e62bae6fe3..e3e5734c6b6 100644 --- a/intern/cycles/blender/addon/properties.py +++ b/intern/cycles/blender/addon/properties.py @@ -1514,9 +1514,12 @@ class CyclesPreferences(bpy.types.AddonPreferences): row.prop(self, "peer_memory") if compute_device_type == 'METAL': - row = layout.row() - row.use_property_split = True - row.prop(self, "use_metalrt") + import platform + # MetalRT only works on Apple Silicon at present, pending argument encoding fixes on AMD + if platform.machine() == 'arm64': + row = layout.row() + row.use_property_split = True + row.prop(self, "use_metalrt") def draw(self, context): diff --git a/intern/cycles/blender/session.cpp b/intern/cycles/blender/session.cpp index 8917c703700..5e9066da5de 100644 --- a/intern/cycles/blender/session.cpp +++ b/intern/cycles/blender/session.cpp @@ -493,8 +493,13 @@ void BlenderSession::render_frame_finish() session->set_output_driver(nullptr); session->full_buffer_written_cb = function_null; - /* The display driver holds OpenGL resources which belong to an OpenGL context held by the render - * engine on Blender side. Force destruction of those resources. */ + /* The display driver is the source of drawing context for both drawing and possible graphics + * interop objects in the path trace. Once the frame is finished the OpenGL context might be + * freed form Blender side. Need to ensure that all GPU resources are freed prior to that + * point. + * Ideally would only do this when OpenGL context is actually destroyed, but there is no way to + * know when this happens (at least in the code at the time when this comment was written). + * The penalty of re-creating resources on every frame is unlikely to be noticed. */ display_driver_ = nullptr; session->set_display_driver(nullptr); diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 9de507966d8..ec50ad9db9a 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -32,7 +32,8 @@ typedef map<string, ConvertNode *> ProxyMap; void BlenderSync::find_shader(BL::ID &id, array<Node *> &used_shaders, Shader *default_shader) { - Shader *shader = (id) ? shader_map.find(id) : default_shader; + Shader *synced_shader = (id) ? shader_map.find(id) : nullptr; + Shader *shader = (synced_shader) ? synced_shader : default_shader; used_shaders.push_back_slow(shader); shader->tag_used(scene); @@ -1573,18 +1574,13 @@ void BlenderSync::sync_lights(BL::Depsgraph &b_depsgraph, bool update_all) } } -void BlenderSync::sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d) +void BlenderSync::sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, bool update_all) { - /* for auto refresh images */ - ImageManager *image_manager = scene->image_manager; - const int frame = b_scene.frame_current(); - const bool auto_refresh_update = image_manager->set_animation_frame_update(frame); - shader_map.pre_sync(); - sync_world(b_depsgraph, b_v3d, auto_refresh_update); - sync_lights(b_depsgraph, auto_refresh_update); - sync_materials(b_depsgraph, auto_refresh_update); + sync_world(b_depsgraph, b_v3d, update_all); + sync_lights(b_depsgraph, update_all); + sync_materials(b_depsgraph, update_all); } CCL_NAMESPACE_END diff --git a/intern/cycles/blender/sync.cpp b/intern/cycles/blender/sync.cpp index 0b11af2dbf9..d4949a5ff30 100644 --- a/intern/cycles/blender/sync.cpp +++ b/intern/cycles/blender/sync.cpp @@ -246,7 +246,12 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, int height, void **python_thread_state) { - if (!has_updates_) { + /* For auto refresh images. */ + ImageManager *image_manager = scene->image_manager; + const int frame = b_scene.frame_current(); + const bool auto_refresh_update = image_manager->set_animation_frame_update(frame); + + if (!has_updates_ && !auto_refresh_update) { return; } @@ -261,7 +266,7 @@ void BlenderSync::sync_data(BL::RenderSettings &b_render, sync_view_layer(b_view_layer); sync_integrator(b_view_layer, background); sync_film(b_view_layer, b_v3d); - sync_shaders(b_depsgraph, b_v3d); + sync_shaders(b_depsgraph, b_v3d, auto_refresh_update); sync_images(); geometry_synced.clear(); /* use for objects and motion sync */ diff --git a/intern/cycles/blender/sync.h b/intern/cycles/blender/sync.h index d92efb80a5d..5cc18452ac1 100644 --- a/intern/cycles/blender/sync.h +++ b/intern/cycles/blender/sync.h @@ -114,7 +114,7 @@ class BlenderSync { /* Shader */ array<Node *> find_used_shaders(BL::Object &b_ob); void sync_world(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, bool update_all); - void sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d); + void sync_shaders(BL::Depsgraph &b_depsgraph, BL::SpaceView3D &b_v3d, bool update_all); void sync_nodes(Shader *shader, BL::ShaderNodeTree &b_ntree); /* Object */ diff --git a/intern/cycles/cmake/external_libs.cmake b/intern/cycles/cmake/external_libs.cmake index c964fbe0d72..6ad64d684c0 100644 --- a/intern/cycles/cmake/external_libs.cmake +++ b/intern/cycles/cmake/external_libs.cmake @@ -479,26 +479,22 @@ else() endif() ########################################################################### -# GLUT +# SDL ########################################################################### if(WITH_CYCLES_STANDALONE AND WITH_CYCLES_STANDALONE_GUI) - if(MSVC AND EXISTS ${_cycles_lib_dir}) - add_definitions(-DFREEGLUT_STATIC -DFREEGLUT_LIB_PRAGMAS=0) - set(GLUT_LIBRARIES "${_cycles_lib_dir}/opengl/lib/freeglut_static.lib") - set(GLUT_INCLUDE_DIR "${_cycles_lib_dir}/opengl/include") - else() - find_package(GLUT) + # We can't use the version from the Blender precompiled libraries because + # it does not include the video subsystem. + find_package(SDL2) - if(NOT GLUT_FOUND) - set(WITH_CYCLES_STANDALONE_GUI OFF) - message(STATUS "GLUT not found, disabling Cycles standalone GUI") - endif() + if(NOT SDL2_FOUND) + set(WITH_CYCLES_STANDALONE_GUI OFF) + message(STATUS "SDL not found, disabling Cycles standalone GUI") endif() include_directories( SYSTEM - ${GLUT_INCLUDE_DIR} + ${SDL2_INCLUDE_DIRS} ) endif() diff --git a/intern/cycles/device/cpu/device_impl.cpp b/intern/cycles/device/cpu/device_impl.cpp index 158a789db5a..612c391f7d5 100644 --- a/intern/cycles/device/cpu/device_impl.cpp +++ b/intern/cycles/device/cpu/device_impl.cpp @@ -191,7 +191,7 @@ device_ptr CPUDevice::mem_alloc_sub_ptr(device_memory &mem, size_t offset, size_ void CPUDevice::const_copy_to(const char *name, void *host, size_t size) { -#if WITH_EMBREE +#ifdef WITH_EMBREE if (strcmp(name, "__data") == 0) { assert(size <= sizeof(KernelData)); diff --git a/intern/cycles/device/hip/device_impl.h b/intern/cycles/device/hip/device_impl.h index 00269ac287c..9afef3789af 100644 --- a/intern/cycles/device/hip/device_impl.h +++ b/intern/cycles/device/hip/device_impl.h @@ -12,8 +12,6 @@ # ifdef WITH_HIP_DYNLOAD # include "hipew.h" -# else -# include "util/opengl.h" # endif CCL_NAMESPACE_BEGIN diff --git a/intern/cycles/device/metal/device_impl.mm b/intern/cycles/device/metal/device_impl.mm index 593c9c3cf06..c01f51fb506 100644 --- a/intern/cycles/device/metal/device_impl.mm +++ b/intern/cycles/device/metal/device_impl.mm @@ -77,11 +77,11 @@ MetalDevice::MetalDevice(const DeviceInfo &info, Stats &stats, Profiler &profile } case METAL_GPU_APPLE: { max_threads_per_threadgroup = 512; + use_metalrt = info.use_metalrt; break; } } - use_metalrt = info.use_metalrt; if (auto metalrt = getenv("CYCLES_METALRT")) { use_metalrt = (atoi(metalrt) != 0); } diff --git a/intern/cycles/integrator/path_trace.cpp b/intern/cycles/integrator/path_trace.cpp index 220d4c9ffa2..eb12b0a6a11 100644 --- a/intern/cycles/integrator/path_trace.cpp +++ b/intern/cycles/integrator/path_trace.cpp @@ -54,14 +54,7 @@ PathTrace::PathTrace(Device *device, PathTrace::~PathTrace() { - /* Destroy any GPU resource which was used for graphics interop. - * Need to have access to the PathTraceDisplay as it is the only source of drawing context which - * is used for interop. */ - if (display_) { - for (auto &&path_trace_work : path_trace_works_) { - path_trace_work->destroy_gpu_resources(display_.get()); - } - } + destroy_gpu_resources(); } void PathTrace::load_kernels() @@ -559,6 +552,11 @@ void PathTrace::set_output_driver(unique_ptr<OutputDriver> driver) void PathTrace::set_display_driver(unique_ptr<DisplayDriver> driver) { + /* The display driver is the source of the drawing context which might be used by + * path trace works. Make sure there is no graphics interop using resources from + * the old display, as it might no longer be available after this call. */ + destroy_gpu_resources(); + if (driver) { display_ = make_unique<PathTraceDisplay>(move(driver)); } @@ -1075,6 +1073,18 @@ bool PathTrace::has_denoised_result() const return render_state_.has_denoised_result; } +void PathTrace::destroy_gpu_resources() +{ + /* Destroy any GPU resource which was used for graphics interop. + * Need to have access to the PathTraceDisplay as it is the only source of drawing context which + * is used for interop. */ + if (display_) { + for (auto &&path_trace_work : path_trace_works_) { + path_trace_work->destroy_gpu_resources(display_.get()); + } + } +} + /* -------------------------------------------------------------------- * Report generation. */ diff --git a/intern/cycles/integrator/path_trace.h b/intern/cycles/integrator/path_trace.h index 1be5ce847bc..a470a6e1402 100644 --- a/intern/cycles/integrator/path_trace.h +++ b/intern/cycles/integrator/path_trace.h @@ -226,6 +226,9 @@ class PathTrace { void progress_set_status(const string &status, const string &substatus = ""); + /* Destroy GPU resources (such as graphics interop) used by work. */ + void destroy_gpu_resources(); + /* Pointer to a device which is configured to be used for path tracing. If multiple devices * are configured this is a `MultiDevice`. */ Device *device_ = nullptr; diff --git a/intern/cycles/integrator/render_scheduler.cpp b/intern/cycles/integrator/render_scheduler.cpp index fe4697e082b..90a5a01320b 100644 --- a/intern/cycles/integrator/render_scheduler.cpp +++ b/intern/cycles/integrator/render_scheduler.cpp @@ -244,7 +244,7 @@ void RenderScheduler::render_work_reschedule_on_cancel(RenderWork &render_work) render_work.tile.write = tile_write; render_work.full.write = full_write; - /* Do not write tile if it has zero samples it it, treat it similarly to all other tiles which + /* Do not write tile if it has zero samples in it, treat it similarly to all other tiles which * got canceled. */ if (!state_.tile_result_was_written && has_rendered_samples) { render_work.tile.write = true; diff --git a/intern/cycles/integrator/render_scheduler.h b/intern/cycles/integrator/render_scheduler.h index 404f65e98a1..dce876d44bd 100644 --- a/intern/cycles/integrator/render_scheduler.h +++ b/intern/cycles/integrator/render_scheduler.h @@ -124,7 +124,7 @@ class RenderScheduler { /* Get sample up to which rendering has been done. * This is an absolute 0-based value. * - * For example, if start sample is 10 and and 5 samples were rendered, then this call will + * For example, if start sample is 10 and 5 samples were rendered, then this call will * return 14. * * If there were no samples rendered, then the behavior is undefined. */ @@ -132,7 +132,7 @@ class RenderScheduler { /* Get number of samples rendered within the current scheduling session. * - * For example, if start sample is 10 and and 5 samples were rendered, then this call will + * For example, if start sample is 10 and 5 samples were rendered, then this call will * return 5. * * Note that this is based on the scheduling information. In practice this means that if someone diff --git a/intern/cycles/kernel/bvh/util.h b/intern/cycles/kernel/bvh/util.h index 1fd3a3f2850..71045157372 100644 --- a/intern/cycles/kernel/bvh/util.h +++ b/intern/cycles/kernel/bvh/util.h @@ -5,27 +5,6 @@ CCL_NAMESPACE_BEGIN -/* Ray offset to avoid self intersection. - * - * This function should be used to compute a modified ray start position for - * rays leaving from a surface. This is from "A Fast and Robust Method for Avoiding - * Self-Intersection" see https://research.nvidia.com/publication/2019-03_A-Fast-and - */ -ccl_device_inline float3 ray_offset(float3 P, float3 Ng) -{ - const float int_scale = 256.0f; - int3 of_i = make_int3((int)(int_scale * Ng.x), (int)(int_scale * Ng.y), (int)(int_scale * Ng.z)); - - float3 p_i = make_float3(__int_as_float(__float_as_int(P.x) + ((P.x < 0) ? -of_i.x : of_i.x)), - __int_as_float(__float_as_int(P.y) + ((P.y < 0) ? -of_i.y : of_i.y)), - __int_as_float(__float_as_int(P.z) + ((P.z < 0) ? -of_i.z : of_i.z))); - const float origin = 1.0f / 32.0f; - const float float_scale = 1.0f / 65536.0f; - return make_float3(fabsf(P.x) < origin ? P.x + float_scale * Ng.x : p_i.x, - fabsf(P.y) < origin ? P.y + float_scale * Ng.y : p_i.y, - fabsf(P.z) < origin ? P.z + float_scale * Ng.z : p_i.z); -} - #if defined(__KERNEL_CPU__) ccl_device int intersections_compare(const void *a, const void *b) { diff --git a/intern/cycles/kernel/geom/point.h b/intern/cycles/kernel/geom/point.h index f7c6cb86c5e..041ecb3c2cf 100644 --- a/intern/cycles/kernel/geom/point.h +++ b/intern/cycles/kernel/geom/point.h @@ -128,9 +128,10 @@ ccl_device float point_radius(KernelGlobals kg, ccl_private const ShaderData *sd return r; } else { - float3 dir = make_float3(r, r, r); + const float normalized_r = r * (1.0f / M_SQRT3_F); + float3 dir = make_float3(normalized_r, normalized_r, normalized_r); object_dir_transform(kg, sd, &dir); - return average(dir); + return len(dir); } } diff --git a/intern/cycles/kernel/integrator/init_from_bake.h b/intern/cycles/kernel/integrator/init_from_bake.h index e616123e9e7..b84059d6676 100644 --- a/intern/cycles/kernel/integrator/init_from_bake.h +++ b/intern/cycles/kernel/integrator/init_from_bake.h @@ -30,6 +30,50 @@ ccl_device_inline float bake_clamp_mirror_repeat(float u, float max) return ((((int)fu) & 1) ? 1.0f - u : u) * max; } +/* Offset towards center of triangle to avoid ray-tracing precision issues. */ +ccl_device const float2 bake_offset_towards_center(KernelGlobals kg, + const int prim, + const float u, + const float v) +{ + float3 tri_verts[3]; + triangle_vertices(kg, prim, tri_verts); + + /* Empirically determined values, by no means perfect. */ + const float position_offset = 1e-4f; + const float uv_offset = 1e-5f; + + /* Offset position towards center, amount relative to absolute size of position coordinates. */ + const float3 P = u * tri_verts[0] + v * tri_verts[1] + (1.0f - u - v) * tri_verts[2]; + const float3 center = (tri_verts[0] + tri_verts[1] + tri_verts[2]) / 3.0f; + const float3 to_center = center - P; + + const float3 offset_P = P + normalize(to_center) * + min(len(to_center), max(max3(fabs(P)), 1.0f) * position_offset); + + /* Compute barycentric coordinates at new position. */ + const float3 v1 = tri_verts[1] - tri_verts[0]; + const float3 v2 = tri_verts[2] - tri_verts[0]; + const float3 vP = offset_P - tri_verts[0]; + + const float d11 = dot(v1, v1); + const float d12 = dot(v1, v2); + const float d22 = dot(v2, v2); + const float dP1 = dot(vP, v1); + const float dP2 = dot(vP, v2); + + const float denom = d11 * d22 - d12 * d12; + if (denom == 0.0f) { + return make_float2(0.0f, 0.0f); + } + + const float offset_v = clamp((d22 * dP1 - d12 * dP2) / denom, uv_offset, 1.0f - uv_offset); + const float offset_w = clamp((d11 * dP2 - d12 * dP1) / denom, uv_offset, 1.0f - uv_offset); + const float offset_u = clamp(1.0f - offset_v - offset_w, uv_offset, 1.0f - uv_offset); + + return make_float2(offset_u, offset_v); +} + /* Return false to indicate that this pixel is finished. * Used by CPU implementation to not attempt to sample pixel for multiple samples once its known * that the pixel did converge. */ @@ -87,7 +131,7 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, /* Initialize path state for path integration. */ path_state_init_integrator(kg, state, sample, rng_hash); - /* Barycentric UV with sub-pixel offset. */ + /* Barycentric UV. */ float u = primitive[2]; float v = primitive[3]; @@ -96,6 +140,14 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg, float dvdx = differential[2]; float dvdy = differential[3]; + /* Exactly at vertex? Nudge inwards to avoid self-intersection. */ + if ((u == 0.0f || u == 1.0f) && (v == 0.0f || v == 1.0f)) { + const float2 uv = bake_offset_towards_center(kg, prim, u, v); + u = uv.x; + v = uv.y; + } + + /* Sub-pixel offset. */ if (sample > 0) { u = bake_clamp_mirror_repeat(u + dudx * (filter_x - 0.5f) + dudy * (filter_y - 0.5f), 1.0f); v = bake_clamp_mirror_repeat(v + dvdx * (filter_x - 0.5f) + dvdy * (filter_y - 0.5f), diff --git a/intern/cycles/kernel/integrator/shade_surface.h b/intern/cycles/kernel/integrator/shade_surface.h index f548d91031d..d2442755646 100644 --- a/intern/cycles/kernel/integrator/shade_surface.h +++ b/intern/cycles/kernel/integrator/shade_surface.h @@ -352,12 +352,8 @@ ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg, float ao_pdf; sample_cos_hemisphere(ao_N, bsdf_u, bsdf_v, &ao_D, &ao_pdf); - if (!(dot(sd->Ng, ao_D) > 0.0f && ao_pdf != 0.0f)) { - return; - } - Ray ray ccl_optional_struct_init; - ray.P = sd->P; + ray.P = shadow_ray_offset(kg, sd, ao_D); ray.D = ao_D; ray.t = kernel_data.integrator.ao_bounces_distance; ray.time = sd->time; diff --git a/intern/cycles/kernel/osl/services.cpp b/intern/cycles/kernel/osl/services.cpp index 16e76b37b0b..85bdb47600e 100644 --- a/intern/cycles/kernel/osl/services.cpp +++ b/intern/cycles/kernel/osl/services.cpp @@ -1638,12 +1638,16 @@ bool OSLRenderServices::trace(TraceOpt &options, ray.D = TO_FLOAT3(R); ray.t = (options.maxdist == 1.0e30f) ? FLT_MAX : options.maxdist - options.mindist; ray.time = sd->time; + ray.self.object = OBJECT_NONE; + ray.self.prim = PRIM_NONE; + ray.self.light_object = OBJECT_NONE; + ray.self.light_prim = PRIM_NONE; if (options.mindist == 0.0f) { /* avoid self-intersections */ if (ray.P == sd->P) { - bool transmit = (dot(sd->Ng, ray.D) < 0.0f); - ray.P = ray_offset(sd->P, (transmit) ? -sd->Ng : sd->Ng); + ray.self.object = sd->object; + ray.self.prim = sd->prim; } } else { diff --git a/intern/cycles/kernel/svm/light_path.h b/intern/cycles/kernel/svm/light_path.h index dce0f83da68..7c2189d3608 100644 --- a/intern/cycles/kernel/svm/light_path.h +++ b/intern/cycles/kernel/svm/light_path.h @@ -58,8 +58,8 @@ ccl_device_noinline void svm_node_light_path(KernelGlobals kg, info = (float)integrator_state_bounce(state, path_flag); } - /* For background, light emission and shadow evaluation we from a - * surface or volume we are effective one bounce further. */ + /* For background, light emission and shadow evaluation from a + * surface or volume we are effectively one bounce further. */ if (path_flag & (PATH_RAY_SHADOW | PATH_RAY_EMISSION)) { info += 1.0f; } diff --git a/intern/cycles/scene/shader.cpp b/intern/cycles/scene/shader.cpp index dde250d5d78..8a08f2a5be9 100644 --- a/intern/cycles/scene/shader.cpp +++ b/intern/cycles/scene/shader.cpp @@ -817,28 +817,28 @@ void ShaderManager::init_xyz_transforms() Transform xyz_to_rgb; if (config->hasRole("aces_interchange")) { - /* Standard OpenColorIO role, defined as ACES2065-1. */ - const Transform xyz_E_to_aces = make_transform(1.0498110175f, - 0.0f, - -0.0000974845f, - 0.0f, - -0.4959030231f, - 1.3733130458f, - 0.0982400361f, - 0.0f, - 0.0f, - 0.0f, - 0.9912520182f, - 0.0f); - const Transform xyz_D65_to_E = make_transform( - 1.0521111f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.9184170f, 0.0f); - + /* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */ Transform aces_to_rgb; if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) { return; } - xyz_to_rgb = aces_to_rgb * xyz_E_to_aces * xyz_D65_to_E; + /* This is the OpenColorIO builtin transform: + * UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */ + const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f, + -0.004451f, + 0.016628f, + 0.000000f, + 0.337369f, + 0.729522f, + -0.066890f, + 0.000000f, + 0.001174f, + -0.003711f, + 1.091595f, + 0.000000f); + const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65); + xyz_to_rgb = aces_to_rgb * xyz_to_aces; } else if (config->hasRole("XYZ")) { /* Custom role used before the standard existed. */ diff --git a/intern/cycles/util/CMakeLists.txt b/intern/cycles/util/CMakeLists.txt index 0e348b1ac0f..fddac1dbbcf 100644 --- a/intern/cycles/util/CMakeLists.txt +++ b/intern/cycles/util/CMakeLists.txt @@ -7,7 +7,6 @@ set(INC ) set(INC_SYS - ${GLEW_INCLUDE_DIR} ) set(SRC @@ -34,14 +33,6 @@ set(LIB ${TBB_LIBRARIES} ) -if(WITH_CYCLES_STANDALONE) - if(WITH_CYCLES_STANDALONE_GUI) - list(APPEND SRC - view.cpp - ) - endif() -endif() - set(SRC_HEADERS algorithm.h aligned_malloc.h @@ -142,7 +133,6 @@ set(SRC_HEADERS unique_ptr.h vector.h version.h - view.h windows.h xml.h ) diff --git a/intern/cycles/util/math.h b/intern/cycles/util/math.h index ed9f230398d..555a5304764 100644 --- a/intern/cycles/util/math.h +++ b/intern/cycles/util/math.h @@ -67,6 +67,9 @@ CCL_NAMESPACE_BEGIN #ifndef M_SQRT2_F # define M_SQRT2_F (1.4142135623730950f) /* sqrt(2) */ #endif +#ifndef M_SQRT3_F +# define M_SQRT3_F (1.7320508075688772f) /* sqrt(3) */ +#endif #ifndef M_LN2_F # define M_LN2_F (0.6931471805599453f) /* ln(2) */ #endif diff --git a/intern/cycles/util/view.cpp b/intern/cycles/util/view.cpp deleted file mode 100644 index 475f8dbcee8..00000000000 --- a/intern/cycles/util/view.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 - * Copyright 2011-2022 Blender Foundation */ - -#include <stdio.h> -#include <stdlib.h> - -#include "util/opengl.h" -#include "util/string.h" -#include "util/time.h" -#include "util/version.h" -#include "util/view.h" - -#ifdef __APPLE__ -# include <GLUT/glut.h> -#else -# include <GL/glut.h> -#endif - -CCL_NAMESPACE_BEGIN - -/* structs */ - -struct View { - ViewInitFunc initf; - ViewExitFunc exitf; - ViewResizeFunc resize; - ViewDisplayFunc display; - ViewKeyboardFunc keyboard; - ViewMotionFunc motion; - - bool first_display; - bool redraw; - - int mouseX, mouseY; - int mouseBut0, mouseBut2; - - int width, height; -} V; - -/* public */ - -static void view_display_text(int x, int y, const char *text) -{ - const char *c; - - glRasterPos3f(x, y, 0); - - for (c = text; *c != '\0'; c++) - glutBitmapCharacter(GLUT_BITMAP_HELVETICA_10, *c); -} - -void view_display_info(const char *info) -{ - const int height = 20; - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0.1f, 0.1f, 0.1f, 0.8f); - glRectf(0.0f, V.height - height, V.width, V.height); - glDisable(GL_BLEND); - - glColor3f(0.5f, 0.5f, 0.5f); - - view_display_text(10, 7 + V.height - height, info); - - glColor3f(1.0f, 1.0f, 1.0f); -} - -void view_display_help() -{ - const int w = (int)((float)V.width / 1.15f); - const int h = (int)((float)V.height / 1.15f); - - const int x1 = (V.width - w) / 2; - const int x2 = x1 + w; - - const int y1 = (V.height - h) / 2; - const int y2 = y1 + h; - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(0.5f, 0.5f, 0.5f, 0.8f); - glRectf(x1, y1, x2, y2); - glDisable(GL_BLEND); - - glColor3f(0.8f, 0.8f, 0.8f); - - string info = string("Cycles Renderer ") + CYCLES_VERSION_STRING; - - view_display_text(x1 + 20, y2 - 20, info.c_str()); - view_display_text(x1 + 20, y2 - 40, "(C) 2011-2022 Blender Foundation"); - view_display_text(x1 + 20, y2 - 80, "Controls:"); - view_display_text(x1 + 20, y2 - 100, "h: Info/Help"); - view_display_text(x1 + 20, y2 - 120, "r: Reset"); - view_display_text(x1 + 20, y2 - 140, "p: Pause"); - view_display_text(x1 + 20, y2 - 160, "esc: Cancel"); - view_display_text(x1 + 20, y2 - 180, "q: Quit program"); - - view_display_text(x1 + 20, y2 - 210, "i: Interactive mode"); - view_display_text(x1 + 20, y2 - 230, "Left mouse: Move camera"); - view_display_text(x1 + 20, y2 - 250, "Right mouse: Rotate camera"); - view_display_text(x1 + 20, y2 - 270, "W/A/S/D: Move camera"); - view_display_text(x1 + 20, y2 - 290, "0/1/2/3: Set max bounces"); - - glColor3f(1.0f, 1.0f, 1.0f); -} - -static void view_display() -{ - if (V.first_display) { - if (V.initf) - V.initf(); - if (V.exitf) - atexit(V.exitf); - - V.first_display = false; - } - - glClearColor(0.05f, 0.05f, 0.05f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, V.width, 0, V.height, -1, 1); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glRasterPos3f(0, 0, 0); - - if (V.display) - V.display(); - - glutSwapBuffers(); -} - -static void view_reshape(int width, int height) -{ - if (width <= 0 || height <= 0) - return; - - V.width = width; - V.height = height; - - glViewport(0, 0, width, height); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - if (V.resize) - V.resize(width, height); -} - -static void view_keyboard(unsigned char key, int x, int y) -{ - if (V.keyboard) - V.keyboard(key); - - if (key == 'm') - printf("mouse %d %d\n", x, y); - if (key == 'q') { - if (V.exitf) - V.exitf(); - exit(0); - } -} - -static void view_mouse(int button, int state, int x, int y) -{ - if (button == 0) { - if (state == GLUT_DOWN) { - V.mouseX = x; - V.mouseY = y; - V.mouseBut0 = 1; - } - else if (state == GLUT_UP) { - V.mouseBut0 = 0; - } - } - else if (button == 2) { - if (state == GLUT_DOWN) { - V.mouseX = x; - V.mouseY = y; - V.mouseBut2 = 1; - } - else if (state == GLUT_UP) { - V.mouseBut2 = 0; - } - } -} - -static void view_motion(int x, int y) -{ - const int but = V.mouseBut0 ? 0 : 2; - const int distX = x - V.mouseX; - const int distY = y - V.mouseY; - - if (V.motion) - V.motion(distX, distY, but); - - V.mouseX = x; - V.mouseY = y; -} - -static void view_idle() -{ - if (V.redraw) { - V.redraw = false; - glutPostRedisplay(); - } - - time_sleep(0.1); -} - -void view_main_loop(const char *title, - int width, - int height, - ViewInitFunc initf, - ViewExitFunc exitf, - ViewResizeFunc resize, - ViewDisplayFunc display, - ViewKeyboardFunc keyboard, - ViewMotionFunc motion) -{ - const char *name = "app"; - char *argv = (char *)name; - int argc = 1; - - memset(&V, 0, sizeof(V)); - V.width = width; - V.height = height; - V.first_display = true; - V.redraw = false; - V.initf = initf; - V.exitf = exitf; - V.resize = resize; - V.display = display; - V.keyboard = keyboard; - V.motion = motion; - - glutInit(&argc, &argv); - glutInitWindowSize(width, height); - glutInitWindowPosition(0, 0); - glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); - glutCreateWindow(title); - - glewInit(); - - view_reshape(width, height); - - glutDisplayFunc(view_display); - glutIdleFunc(view_idle); - glutReshapeFunc(view_reshape); - glutKeyboardFunc(view_keyboard); - glutMouseFunc(view_mouse); - glutMotionFunc(view_motion); - - glutMainLoop(); -} - -void view_redraw() -{ - V.redraw = true; -} - -CCL_NAMESPACE_END diff --git a/intern/cycles/util/view.h b/intern/cycles/util/view.h deleted file mode 100644 index 51c242c21f7..00000000000 --- a/intern/cycles/util/view.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 - * Copyright 2011-2022 Blender Foundation */ - -#ifndef __UTIL_VIEW_H__ -#define __UTIL_VIEW_H__ - -/* Functions to display a simple OpenGL window using GLUT, simplified to the - * bare minimum we need to reduce boilerplate code in tests apps. */ - -CCL_NAMESPACE_BEGIN - -typedef void (*ViewInitFunc)(); -typedef void (*ViewExitFunc)(); -typedef void (*ViewResizeFunc)(int width, int height); -typedef void (*ViewDisplayFunc)(); -typedef void (*ViewKeyboardFunc)(unsigned char key); -typedef void (*ViewMotionFunc)(int x, int y, int button); - -void view_main_loop(const char *title, - int width, - int height, - ViewInitFunc initf, - ViewExitFunc exitf, - ViewResizeFunc resize, - ViewDisplayFunc display, - ViewKeyboardFunc keyboard, - ViewMotionFunc motion); - -void view_display_info(const char *info); -void view_display_help(); -void view_redraw(); - -CCL_NAMESPACE_END - -#endif /*__UTIL_VIEW_H__*/ |