diff --git a/source/blender/compositor/realtime_compositor/COM_context.hh b/source/blender/compositor/realtime_compositor/COM_context.hh index 7ca549072cf..1aee853008a 100644 --- a/source/blender/compositor/realtime_compositor/COM_context.hh +++ b/source/blender/compositor/realtime_compositor/COM_context.hh @@ -69,7 +69,7 @@ class Context { virtual const RenderData &get_render_data() const = 0; /* Get the width and height of the render passes and of the output texture returned by the - * get_input_texture and get_output_texture methods respectively. */ + * get_pass and get_output_texture methods respectively. */ virtual int2 get_render_size() const = 0; /* Get the rectangular region representing the area of the input that the compositor will operate @@ -91,11 +91,8 @@ class Context { bool is_data, ResultPrecision precision) = 0; - /* Get the texture where the given render pass is stored. This should be called by the Render - * Layer node to populate its outputs. */ - virtual GPUTexture *get_input_texture(const Scene *scene, - int view_layer, - const char *pass_name) = 0; + /* Get the result where the given render pass is stored. */ + virtual Result get_pass(const Scene *scene, int view_layer, const char *pass_name) = 0; /* Get the name of the view currently being rendered. */ virtual StringRef get_view_name() const = 0; diff --git a/source/blender/compositor/realtime_compositor/COM_result.hh b/source/blender/compositor/realtime_compositor/COM_result.hh index 08ff538b84c..547b955fe71 100644 --- a/source/blender/compositor/realtime_compositor/COM_result.hh +++ b/source/blender/compositor/realtime_compositor/COM_result.hh @@ -169,6 +169,10 @@ class Result { /* Construct a result of the given type and precision within the given context. */ Result(Context &context, ResultType type, ResultPrecision precision); + /* Construct a result of an appropriate type and precision based on the given GPU texture format + * within the given context. */ + Result(Context &context, eGPUTextureFormat format); + /* Returns the appropriate GPU texture format based on the given result type and precision. */ static eGPUTextureFormat gpu_texture_format(ResultType type, ResultPrecision precision); diff --git a/source/blender/compositor/realtime_compositor/intern/result.cc b/source/blender/compositor/realtime_compositor/intern/result.cc index e2b174035be..183a5a05962 100644 --- a/source/blender/compositor/realtime_compositor/intern/result.cc +++ b/source/blender/compositor/realtime_compositor/intern/result.cc @@ -26,6 +26,11 @@ Result::Result(Context &context, ResultType type, ResultPrecision precision) { } +Result::Result(Context &context, eGPUTextureFormat format) + : context_(&context), type_(Result::type(format)), precision_(Result::precision(format)) +{ +} + eGPUTextureFormat Result::gpu_texture_format(ResultType type, ResultPrecision precision) { switch (precision) { diff --git a/source/blender/draw/engines/compositor/compositor_engine.cc b/source/blender/draw/engines/compositor/compositor_engine.cc index 23520dca928..5d69547b3a9 100644 --- a/source/blender/draw/engines/compositor/compositor_engine.cc +++ b/source/blender/draw/engines/compositor/compositor_engine.cc @@ -152,37 +152,40 @@ class Context : public realtime_compositor::Context { return result; } - GPUTexture *get_input_texture(const Scene *scene, int view_layer, const char *pass_name) override + realtime_compositor::Result get_pass(const Scene *scene, + int view_layer, + const char *pass_name) override { if (DEG_get_original_id(const_cast(&scene->id)) != DEG_get_original_id(&DRW_context_state_get()->scene->id)) { - return nullptr; + return realtime_compositor::Result(*this); } if (view_layer != 0) { - return nullptr; + return realtime_compositor::Result(*this); } /* The combined pass is a special case where we return the viewport color texture, because it * includes Grease Pencil objects since GP is drawn using their own engine. */ if (STREQ(pass_name, RE_PASSNAME_COMBINED)) { - return DRW_viewport_texture_list_get()->color; + GPUTexture *combined_texture = DRW_viewport_texture_list_get()->color; + realtime_compositor::Result pass = realtime_compositor::Result( + *this, GPU_texture_format(combined_texture)); + pass.wrap_external(combined_texture); + return pass; } /* Return the pass that was written by the engine if such pass was found. */ GPUTexture *pass_texture = DRW_viewport_pass_texture_get(pass_name).gpu_texture(); if (pass_texture) { - return pass_texture; + realtime_compositor::Result pass = realtime_compositor::Result( + *this, GPU_texture_format(pass_texture)); + pass.wrap_external(pass_texture); + return pass; } - /* If no Z pass was found above, return the viewport depth as a fallback, which might be - * populated if overlays are enabled. */ - if (STREQ(pass_name, RE_PASSNAME_Z)) { - return DRW_viewport_texture_list_get()->depth; - } - - return nullptr; + return realtime_compositor::Result(*this); } StringRef get_view_name() const override diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc index dcffc62ac32..9bfedca58c1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc @@ -572,8 +572,7 @@ class CryptoMatteOperation : public BaseCryptoMatteOperation { const int cryptomatte_layers_count = int(math::ceil(view_layer->cryptomatte_levels / 2.0f)); for (int i = 0; i < cryptomatte_layers_count; i++) { const std::string pass_name = fmt::format("{}{:02}", cryptomatte_type, i); - GPUTexture *pass_texture = context().get_input_texture( - scene, view_layer_index, pass_name.c_str()); + GPUTexture *pass_texture = context().get_pass(scene, view_layer_index, pass_name.c_str()); /* If this Cryptomatte layer wasn't found, then all later Cryptomatte layers can't be used * even if they were found. */ diff --git a/source/blender/nodes/composite/nodes/node_composite_image.cc b/source/blender/nodes/composite/nodes/node_composite_image.cc index 67692603a9e..efe16599815 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.cc +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -8,6 +8,7 @@ #include "node_composite_util.hh" +#include "BLI_assert.h" #include "BLI_linklist.h" #include "BLI_math_vector_types.hh" #include "BLI_rect.h" @@ -665,104 +666,135 @@ class RenderLayerOperation : public NodeOperation { void execute() override { - /* Not yet supported on CPU. */ - if (!context().use_gpu()) { - for (const bNodeSocket *output : this->node()->output_sockets()) { - Result &output_result = get_result(output->identifier); - if (output_result.should_compute()) { - output_result.allocate_invalid(); - } - } - return; - } + const Scene *scene = reinterpret_cast(this->bnode().id); + const int view_layer = this->bnode().custom1; - const Scene *scene = reinterpret_cast(bnode().id); - const int view_layer = bnode().custom1; - - Result &image_result = get_result("Image"); - Result &alpha_result = get_result("Alpha"); + Result &image_result = this->get_result("Image"); + Result &alpha_result = this->get_result("Alpha"); if (image_result.should_compute() || alpha_result.should_compute()) { - GPUTexture *combined_texture = context().get_input_texture( + const Result combined_pass = this->context().get_pass( scene, view_layer, RE_PASSNAME_COMBINED); if (image_result.should_compute()) { - execute_pass(image_result, combined_texture, "compositor_read_input_color"); + this->execute_pass(combined_pass, image_result); } if (alpha_result.should_compute()) { - execute_pass(alpha_result, combined_texture, "compositor_read_input_alpha"); + this->execute_pass(combined_pass, alpha_result); } } - /* Other output passes are not supported for now, so allocate them as invalid. */ for (const bNodeSocket *output : this->node()->output_sockets()) { if (STR_ELEM(output->identifier, "Image", "Alpha")) { continue; } - Result &result = get_result(output->identifier); + Result &result = this->get_result(output->identifier); if (!result.should_compute()) { continue; } - context().populate_meta_data_for_pass( + this->context().populate_meta_data_for_pass( scene, view_layer, output->identifier, result.meta_data); - GPUTexture *pass_texture = context().get_input_texture( - scene, view_layer, output->identifier); - if (output->type == SOCK_FLOAT) { - execute_pass(result, pass_texture, "compositor_read_input_float"); - } - else if (output->type == SOCK_VECTOR) { - execute_pass(result, pass_texture, "compositor_read_input_vector"); - } - else if (output->type == SOCK_RGBA) { - execute_pass(result, pass_texture, "compositor_read_input_color"); - } - else { - BLI_assert_unreachable(); - } + const Result pass = this->context().get_pass(scene, view_layer, output->identifier); + this->execute_pass(pass, result); } } - void execute_pass(Result &result, GPUTexture *pass_texture, const char *shader_name) + void execute_pass(const Result &pass, Result &result) { - if (pass_texture == nullptr) { + if (!pass.is_allocated()) { /* Pass not rendered yet, or not supported by viewport. */ result.allocate_invalid(); - context().set_info_message("Viewport compositor setup not fully supported"); + this->context().set_info_message("Viewport compositor setup not fully supported"); return; } - if (!context().is_valid_compositing_region()) { + if (!this->context().is_valid_compositing_region()) { result.allocate_invalid(); return; } - const ResultPrecision precision = Result::precision(GPU_texture_format(pass_texture)); - GPUShader *shader = context().get_shader(shader_name, precision); + if (this->context().use_gpu()) { + this->execute_pass_gpu(pass, result); + } + else { + this->execute_pass_cpu(pass, result); + } + } + + void execute_pass_gpu(const Result &pass, Result &result) + { + result.set_precision(pass.precision()); + + GPUShader *shader = this->context().get_shader(this->get_shader_name(pass, result), + result.precision()); GPU_shader_bind(shader); /* The compositing space might be limited to a subset of the pass texture, so only read that - * compositing region into an appropriately sized texture. */ - const rcti compositing_region = context().get_compositing_region(); + * compositing region into an appropriately sized result. */ + const rcti compositing_region = this->context().get_compositing_region(); const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); - const int input_unit = GPU_shader_get_sampler_binding(shader, "input_tx"); - GPU_texture_bind(pass_texture, input_unit); + pass.bind_as_texture(shader, "input_tx"); - result.set_precision(precision); - - const int2 compositing_region_size = context().get_compositing_region_size(); - result.allocate_texture(Domain(compositing_region_size)); + result.allocate_texture(Domain(this->context().get_compositing_region_size())); result.bind_as_image(shader, "output_img"); - compute_dispatch_threads_at_least(shader, compositing_region_size); + compute_dispatch_threads_at_least(shader, result.domain().size); GPU_shader_unbind(); - GPU_texture_unbind(pass_texture); + pass.unbind_as_texture(); result.unbind_as_image(); } + + const char *get_shader_name(const Result &pass, const Result &result) + { + /* Special case for alpha output. */ + if (pass.type() == ResultType::Color && result.type() == ResultType::Float) { + return "compositor_read_input_alpha"; + } + + switch (pass.type()) { + case ResultType::Float: + return "compositor_read_input_float"; + case ResultType::Vector: + return "compositor_read_input_vector"; + case ResultType::Color: + return "compositor_read_input_color"; + case ResultType::Float3: + return "compositor_read_input_vector"; + default: + /* Other types are internal and needn't be handled by operations. */ + break; + } + + BLI_assert_unreachable(); + return nullptr; + } + + void execute_pass_cpu(const Result &pass, Result &result) + { + /* The compositing space might be limited to a subset of the pass texture, so only read that + * compositing region into an appropriately sized result. */ + const rcti compositing_region = this->context().get_compositing_region(); + const int2 lower_bound = int2(compositing_region.xmin, compositing_region.ymin); + + result.allocate_texture(Domain(this->context().get_compositing_region_size())); + + /* Special case for alpha output. */ + if (pass.type() == ResultType::Color && result.type() == ResultType::Float) { + parallel_for(result.domain().size, [&](const int2 texel) { + result.store_pixel(texel, float4(pass.load_pixel(texel + lower_bound).w)); + }); + } + else { + parallel_for(result.domain().size, [&](const int2 texel) { + result.store_pixel(texel, pass.load_pixel(texel + lower_bound)); + }); + } + } }; static NodeOperation *get_compositor_operation(Context &context, DNode node) diff --git a/source/blender/render/intern/compositor.cc b/source/blender/render/intern/compositor.cc index c026302de5c..6b17d294986 100644 --- a/source/blender/render/intern/compositor.cc +++ b/source/blender/render/intern/compositor.cc @@ -160,8 +160,11 @@ class Context : public realtime_compositor::Context { /* Viewer output result. */ realtime_compositor::Result viewer_output_result_; - /* Cached textures that the compositor took ownership of. */ - Vector textures_; + /* Cached GPU and CPU passes that the compositor took ownership of. Those had their reference + * count incremented when accessed and need to be freed/have their reference count decremented + * when destroying the context. */ + Vector cached_gpu_passes_; + Vector cached_cpu_passes_; public: Context(const ContextInputData &input_data, TexturePool &texture_pool) @@ -176,8 +179,11 @@ class Context : public realtime_compositor::Context { { output_result_.release(); viewer_output_result_.release(); - for (GPUTexture *texture : textures_) { - GPU_texture_free(texture); + for (GPUTexture *pass : cached_gpu_passes_) { + GPU_texture_free(pass); + } + for (ImBuf *pass : cached_cpu_passes_) { + IMB_freeImBuf(pass); } } @@ -284,61 +290,71 @@ class Context : public realtime_compositor::Context { return viewer_output_result_; } - GPUTexture *get_input_texture(const Scene *scene, - int view_layer_id, - const char *pass_name) override + realtime_compositor::Result get_pass(const Scene *scene, + int view_layer_id, + const char *pass_name) override { if (!scene) { - return nullptr; + return realtime_compositor::Result(*this); } ViewLayer *view_layer = static_cast( BLI_findlink(&scene->view_layers, view_layer_id)); if (!view_layer) { - return nullptr; + return realtime_compositor::Result(*this); } Render *render = RE_GetSceneRender(scene); if (!render) { - return nullptr; + return realtime_compositor::Result(*this); } RenderResult *render_result = RE_AcquireResultRead(render); if (!render_result) { RE_ReleaseResult(render); - return nullptr; + return realtime_compositor::Result(*this); } RenderLayer *render_layer = RE_GetRenderLayer(render_result, view_layer->name); if (!render_layer) { RE_ReleaseResult(render); - return nullptr; + return realtime_compositor::Result(*this); } RenderPass *render_pass = RE_pass_find_by_name( render_layer, pass_name, this->get_view_name().data()); if (!render_pass) { RE_ReleaseResult(render); - return nullptr; + return realtime_compositor::Result(*this); } if (!render_pass || !render_pass->ibuf || !render_pass->ibuf->float_buffer.data) { RE_ReleaseResult(render); - return nullptr; + return realtime_compositor::Result(*this); } - GPUTexture *texture = RE_pass_ensure_gpu_texture_cache(render, render_pass); - if (!texture) { - RE_ReleaseResult(render); - return nullptr; - } + const eGPUTextureFormat format = (render_pass->channels == 1) ? GPU_R32F : + (render_pass->channels == 3) ? GPU_RGB32F : + GPU_RGBA32F; + realtime_compositor::Result pass = realtime_compositor::Result(*this, format); - /* Don't assume render keeps texture around, add our own reference. */ - GPU_texture_ref(texture); - textures_.append(texture); + if (this->use_gpu()) { + GPUTexture *pass_texture = RE_pass_ensure_gpu_texture_cache(render, render_pass); + /* Don't assume render will keep pass data stored, add our own reference. */ + GPU_texture_ref(pass_texture); + pass.wrap_external(pass_texture); + cached_gpu_passes_.append(pass_texture); + } + else { + /* Don't assume render will keep pass data stored, add our own reference. */ + IMB_refImBuf(render_pass->ibuf); + pass.wrap_external(render_pass->ibuf->float_buffer.data, + int2(render_pass->ibuf->x, render_pass->ibuf->y)); + cached_cpu_passes_.append(render_pass->ibuf); + } RE_ReleaseResult(render); - return texture; + return pass; } StringRef get_view_name() const override