Add Chromium-only Blender WebEngine parity work
This commit is contained in:
1091
blender-5.2.0/source/blender/render/intern/bake.cc
Normal file
1091
blender-5.2.0/source/blender/render/intern/bake.cc
Normal file
File diff suppressed because it is too large
Load Diff
860
blender-5.2.0/source/blender/render/intern/compositor.cc
Normal file
860
blender-5.2.0/source/blender/render/intern/compositor.cc
Normal file
@@ -0,0 +1,860 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_memory_utils.hh"
|
||||
#include "BLI_threads.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
|
||||
#include "BKE_cryptomatte.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
#include "BKE_scene.hh"
|
||||
#include "BKE_scene_runtime.hh"
|
||||
|
||||
#include "DRW_engine.hh"
|
||||
#include "DRW_render.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_conversion_operation.hh"
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_node_group_operation.hh"
|
||||
#include "COM_realize_on_domain_operation.hh"
|
||||
#include "COM_render_context.hh"
|
||||
#include "COM_result.hh"
|
||||
|
||||
#include "NOD_eval_log.hh"
|
||||
|
||||
#include "RE_compositor.hh"
|
||||
#include "RE_pipeline.h"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "GPU_context.hh"
|
||||
#include "GPU_state.hh"
|
||||
#include "GPU_texture_pool.hh"
|
||||
|
||||
#include "render_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace render {
|
||||
|
||||
/**
|
||||
* Render Context Data
|
||||
*
|
||||
* Stored separately from the context so we can update it without losing any cached
|
||||
* data from the context.
|
||||
*/
|
||||
class ContextInputData {
|
||||
public:
|
||||
const Render *render;
|
||||
const Main *main;
|
||||
const Scene *scene;
|
||||
const RenderData *render_data;
|
||||
const bNodeTree *node_tree;
|
||||
std::string view_name;
|
||||
compositor::RenderContext *render_context;
|
||||
compositor::NodeGroupOutputTypes needed_outputs;
|
||||
|
||||
ContextInputData(const Render *render,
|
||||
const Main &main,
|
||||
const Scene &scene,
|
||||
const RenderData &render_data,
|
||||
const bNodeTree &node_tree,
|
||||
const char *view_name,
|
||||
compositor::RenderContext *render_context,
|
||||
compositor::NodeGroupOutputTypes needed_outputs)
|
||||
: render(render),
|
||||
main(&main),
|
||||
scene(&scene),
|
||||
render_data(&render_data),
|
||||
node_tree(&node_tree),
|
||||
view_name(view_name),
|
||||
render_context(render_context),
|
||||
needed_outputs(needed_outputs)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/* Render Context Data */
|
||||
|
||||
class Context : public compositor::Context {
|
||||
private:
|
||||
/* Input data. */
|
||||
ContextInputData input_data_;
|
||||
|
||||
/* 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<gpu::Texture *> cached_gpu_passes_;
|
||||
Vector<ImBuf *> cached_cpu_passes_;
|
||||
|
||||
/* True if GPU compute is supported and can be used, if false, we fallback to CPU. */
|
||||
bool gpu_supported_ = true;
|
||||
|
||||
public:
|
||||
Context(compositor::StaticCacheManager &cache_manager, const ContextInputData &input_data)
|
||||
: compositor::Context(cache_manager), input_data_(input_data)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~Context()
|
||||
{
|
||||
for (gpu::Texture *pass : cached_gpu_passes_) {
|
||||
GPU_texture_free(pass);
|
||||
}
|
||||
for (ImBuf *pass : cached_cpu_passes_) {
|
||||
IMB_freeImBuf(pass);
|
||||
}
|
||||
}
|
||||
|
||||
const Main &get_main() const override
|
||||
{
|
||||
return *input_data_.main;
|
||||
}
|
||||
|
||||
const Scene &get_scene() const override
|
||||
{
|
||||
return *input_data_.scene;
|
||||
}
|
||||
|
||||
void set_gpu_supported(const bool supported)
|
||||
{
|
||||
gpu_supported_ = supported;
|
||||
}
|
||||
|
||||
bool use_gpu() const override
|
||||
{
|
||||
return gpu_supported_ &&
|
||||
this->get_render_data().compositor_device == SCE_COMPOSITOR_DEVICE_GPU;
|
||||
}
|
||||
|
||||
compositor::NodeGroupOutputTypes needed_outputs() const
|
||||
{
|
||||
return input_data_.needed_outputs;
|
||||
}
|
||||
|
||||
const RenderData &get_render_data() const override
|
||||
{
|
||||
return *(input_data_.render_data);
|
||||
}
|
||||
|
||||
int2 get_render_size() const
|
||||
{
|
||||
Render *render = RE_GetSceneRender(input_data_.scene);
|
||||
RenderResult *render_result = RE_AcquireResultRead(render);
|
||||
|
||||
/* If a render result already exist, use its size, since the compositor operates on the render
|
||||
* settings at which the render happened. Otherwise, use the size from the render data. */
|
||||
int2 size;
|
||||
if (render_result) {
|
||||
size = int2(render_result->rectx, render_result->recty);
|
||||
}
|
||||
else {
|
||||
BKE_render_resolution(input_data_.render_data, true, &size.x, &size.y);
|
||||
}
|
||||
|
||||
RE_ReleaseResult(render);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
compositor::Domain get_compositing_domain() const override
|
||||
{
|
||||
return compositor::Domain(this->get_render_size());
|
||||
}
|
||||
|
||||
void write_output(const compositor::Result &result)
|
||||
{
|
||||
Render *render = RE_GetSceneRender(input_data_.scene);
|
||||
RenderResult *render_result = RE_AcquireResultWrite(render);
|
||||
|
||||
if (render_result) {
|
||||
RenderView *render_view = RE_RenderViewGetByName(render_result,
|
||||
input_data_.view_name.c_str());
|
||||
ImBuf *image_buffer = RE_RenderViewEnsureImBuf(render_result, render_view);
|
||||
render_result->have_combined = true;
|
||||
|
||||
if (result.is_single_value()) {
|
||||
float *data = MEM_new_array_uninitialized<float>(
|
||||
4 * size_t(render_result->rectx) * size_t(render_result->recty), __func__);
|
||||
image_buffer->assign_float_data(data);
|
||||
IMB_rectfill(image_buffer, result.get_single_value<compositor::Color>());
|
||||
}
|
||||
else if (this->use_gpu()) {
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
float *output_buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
|
||||
image_buffer->assign_float_data(output_buffer);
|
||||
}
|
||||
else {
|
||||
if (result.sharing_info()) {
|
||||
image_buffer->float_buffer = ImBufFloatBuffer{
|
||||
.data = static_cast<const float *>(result.cpu_data().data()),
|
||||
.sharing_info = result.sharing_info(),
|
||||
.colorspace = nullptr};
|
||||
}
|
||||
else {
|
||||
float *data = MEM_new_array_uninitialized<float>(
|
||||
4 * size_t(render_result->rectx) * size_t(render_result->recty), __func__);
|
||||
image_buffer->assign_float_data(data);
|
||||
std::memcpy(image_buffer->float_data_for_write(),
|
||||
result.cpu_data().data(),
|
||||
render_result->rectx * render_result->recty * 4 * sizeof(float));
|
||||
}
|
||||
}
|
||||
}
|
||||
RE_ReleaseResult(render);
|
||||
|
||||
Image *image = BKE_image_ensure_viewer(G.main, IMA_TYPE_R_RESULT, "Render Result");
|
||||
BKE_image_partial_update_mark_full_update(image);
|
||||
BLI_thread_lock(LOCK_DRAW_IMAGE);
|
||||
BKE_image_signal(G.main, image, nullptr, IMA_SIGNAL_FREE);
|
||||
BLI_thread_unlock(LOCK_DRAW_IMAGE);
|
||||
}
|
||||
|
||||
void write_viewer_image(const compositor::Result &viewer_result)
|
||||
{
|
||||
Image *image = BKE_image_ensure_viewer(G.main, IMA_TYPE_COMPOSITE, "Viewer Node");
|
||||
|
||||
if (viewer_result.meta_data.is_non_color_data) {
|
||||
image->flag &= ~IMA_VIEW_AS_RENDER;
|
||||
}
|
||||
else {
|
||||
image->flag |= IMA_VIEW_AS_RENDER;
|
||||
}
|
||||
|
||||
ImageUser image_user = {nullptr};
|
||||
image_user.multi_index = BKE_scene_multiview_view_id_get(input_data_.render_data,
|
||||
input_data_.view_name.c_str());
|
||||
|
||||
if (BKE_scene_multiview_is_render_view_first(input_data_.render_data,
|
||||
input_data_.view_name.c_str()))
|
||||
{
|
||||
BKE_image_ensure_viewer_views(input_data_.render_data, image, &image_user);
|
||||
}
|
||||
|
||||
BLI_thread_lock(LOCK_DRAW_IMAGE);
|
||||
|
||||
void *lock;
|
||||
ImBuf *image_buffer = BKE_image_acquire_ibuf_gpu(image, &image_user, &lock);
|
||||
|
||||
const int2 size = viewer_result.is_single_value() ? this->get_render_size() :
|
||||
viewer_result.domain().data_size;
|
||||
|
||||
/* The image buffer has a different size than the viewer result, set the new size and free all
|
||||
* data to be reallocated later. */
|
||||
if (int2(image_buffer->x, image_buffer->y) != size) {
|
||||
IMB_free_byte_pixels(image_buffer);
|
||||
IMB_free_float_pixels(image_buffer);
|
||||
IMB_free_gpu_textures(image_buffer);
|
||||
image_buffer->x = size.x;
|
||||
image_buffer->y = size.y;
|
||||
}
|
||||
|
||||
if (this->use_gpu()) {
|
||||
/* If using GPU, free any potential previous CPU data. */
|
||||
IMB_free_float_pixels(image_buffer);
|
||||
|
||||
/* Allocate a GPU texture if using GPU and no texture exists or one exists but with a
|
||||
* different format. */
|
||||
if (!image_buffer->gpu.texture ||
|
||||
GPU_texture_format(image_buffer->gpu.texture) != viewer_result.get_gpu_texture_format())
|
||||
{
|
||||
gpu::TextureFormat format = viewer_result.get_gpu_texture_format();
|
||||
gpu::Texture *texture = GPU_texture_create_2d(
|
||||
__func__, size.x, size.y, 1, format, GPU_TEXTURE_USAGE_GENERAL, nullptr);
|
||||
IMB_assign_gpu_texture(image_buffer, texture);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* If not using GPU, free any potential previous GPU data. */
|
||||
IMB_free_gpu_textures(image_buffer);
|
||||
}
|
||||
|
||||
if (this->use_gpu()) {
|
||||
if (viewer_result.is_single_value()) {
|
||||
GPU_texture_clear(image_buffer->gpu.texture,
|
||||
GPU_DATA_FLOAT,
|
||||
viewer_result.get_single_value<compositor::Color>());
|
||||
}
|
||||
else {
|
||||
GPU_texture_copy(image_buffer->gpu.texture, viewer_result);
|
||||
}
|
||||
image_buffer->userflags |= IB_HOST_BUFFER_INVALID;
|
||||
}
|
||||
else {
|
||||
if (viewer_result.is_single_value()) {
|
||||
IMB_alloc_float_pixels(image_buffer, 4, false);
|
||||
IMB_rectfill(image_buffer, viewer_result.get_single_value<compositor::Color>());
|
||||
}
|
||||
else if (viewer_result.sharing_info()) {
|
||||
image_buffer->channels = 4;
|
||||
image_buffer->float_buffer = ImBufFloatBuffer{
|
||||
.data = static_cast<const float *>(viewer_result.cpu_data().data()),
|
||||
.sharing_info = viewer_result.sharing_info(),
|
||||
.colorspace = nullptr};
|
||||
}
|
||||
else if (viewer_result.cpu_data().data() != image_buffer->float_data()) {
|
||||
IMB_alloc_float_pixels(image_buffer, 4, false);
|
||||
std::memcpy(image_buffer->float_data_for_write(),
|
||||
viewer_result.cpu_data().data(),
|
||||
size.x * size.y * 4 * sizeof(float));
|
||||
}
|
||||
image_buffer->userflags |= IB_DISPLAY_BUFFER_INVALID;
|
||||
}
|
||||
|
||||
if (!viewer_result.is_single_value()) {
|
||||
image_buffer->flags |= ImBufFlags::HasDisplayWindow;
|
||||
const int2 display_offset = int2(viewer_result.domain().transformation.location());
|
||||
copy_v2_v2_int(image_buffer->display_size, viewer_result.domain().display_size);
|
||||
copy_v2_v2_int(image_buffer->display_offset, display_offset);
|
||||
copy_v2_v2_int(image_buffer->data_offset, viewer_result.domain().data_offset);
|
||||
}
|
||||
else {
|
||||
image_buffer->flags &= ~ImBufFlags::HasDisplayWindow;
|
||||
}
|
||||
|
||||
BKE_image_partial_update_mark_full_update(image);
|
||||
BKE_image_release_ibuf(image, image_buffer, lock);
|
||||
BLI_thread_unlock(LOCK_DRAW_IMAGE);
|
||||
}
|
||||
|
||||
void write_viewer(compositor::Result &viewer_result) override
|
||||
{
|
||||
using namespace compositor;
|
||||
|
||||
/* Realize the transforms if needed. */
|
||||
const InputDescriptor input_descriptor = {ResultType::Color,
|
||||
InputRealizationMode::OperationDomain};
|
||||
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
|
||||
*this, viewer_result, input_descriptor, viewer_result.domain());
|
||||
|
||||
if (realization_operation) {
|
||||
Result realize_input = this->create_result(ResultType::Color, viewer_result.precision());
|
||||
realize_input.share_data(viewer_result);
|
||||
realization_operation->map_input_to_result(&realize_input);
|
||||
realization_operation->evaluate();
|
||||
|
||||
Result &realized_viewer_result = realization_operation->get_result();
|
||||
this->write_viewer_image(realized_viewer_result);
|
||||
realized_viewer_result.release();
|
||||
delete realization_operation;
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_viewer_image(viewer_result);
|
||||
}
|
||||
|
||||
compositor::ResultType get_pass_data_type(const RenderPass *pass)
|
||||
{
|
||||
switch (pass->channels) {
|
||||
case 1:
|
||||
return compositor::ResultType::Float;
|
||||
case 2:
|
||||
return compositor::ResultType::Float2;
|
||||
case 3:
|
||||
return compositor::ResultType::Float3;
|
||||
case 4:
|
||||
if (StringRef(pass->chan_id) == "XYZW") {
|
||||
return compositor::ResultType::Float4;
|
||||
}
|
||||
else {
|
||||
return compositor::ResultType::Color;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return compositor::ResultType::Float;
|
||||
}
|
||||
|
||||
compositor::ResultType get_pass_type(const RenderPass *pass)
|
||||
{
|
||||
switch (pass->channels) {
|
||||
case 1:
|
||||
return compositor::ResultType::Float;
|
||||
case 2:
|
||||
return compositor::ResultType::Float2;
|
||||
case 3:
|
||||
if (StringRef(pass->chan_id) == "RGB") {
|
||||
return compositor::ResultType::Color;
|
||||
}
|
||||
else {
|
||||
return compositor::ResultType::Float3;
|
||||
}
|
||||
case 4:
|
||||
if (StringRef(pass->chan_id) == "XYZW") {
|
||||
return compositor::ResultType::Float4;
|
||||
}
|
||||
else {
|
||||
return compositor::ResultType::Color;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return compositor::ResultType::Float;
|
||||
}
|
||||
|
||||
compositor::Result get_invalid_pass()
|
||||
{
|
||||
compositor::Result invalid_pass = this->create_result(compositor::ResultType::Color);
|
||||
invalid_pass.allocate_invalid();
|
||||
return invalid_pass;
|
||||
}
|
||||
|
||||
compositor::Result get_pass(const Scene *scene, int view_layer_id, const char *name) override
|
||||
{
|
||||
/* Blender aliases the Image pass name to be the Combined pass, so we return the combined pass
|
||||
* in that case. */
|
||||
const char *pass_name = StringRef(name) == "Image" ? "Combined" : name;
|
||||
|
||||
if (!scene) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
ViewLayer *view_layer = static_cast<ViewLayer *>(
|
||||
BLI_findlink(&scene->view_layers, view_layer_id));
|
||||
if (!view_layer) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
Render *render = RE_GetSceneRender(scene);
|
||||
if (!render) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
BLI_SCOPED_DEFER([&]() { RE_ReleaseResult(render); });
|
||||
|
||||
RenderResult *render_result = RE_AcquireResultRead(render);
|
||||
if (!render_result) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
RenderLayer *render_layer = RE_GetRenderLayer(render_result, view_layer->name);
|
||||
if (!render_layer) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
RenderPass *render_pass = RE_pass_find_by_name(
|
||||
render_layer, pass_name, this->get_view_name().data());
|
||||
if (!render_pass) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
if (!render_pass || !render_pass->ibuf || !render_pass->ibuf->float_data()) {
|
||||
return this->get_invalid_pass();
|
||||
}
|
||||
|
||||
compositor::Result pass_data = compositor::Result(
|
||||
*this, this->get_pass_data_type(render_pass), compositor::ResultPrecision::Full);
|
||||
|
||||
if (this->use_gpu()) {
|
||||
gpu::Texture *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_data.share_data(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_data.share_data(render_pass->ibuf->float_buffer.data,
|
||||
int2(render_pass->ibuf->x, render_pass->ibuf->y),
|
||||
render_pass->ibuf->float_buffer.sharing_info);
|
||||
cached_cpu_passes_.append(render_pass->ibuf);
|
||||
}
|
||||
|
||||
compositor::Result pass = compositor::Result(
|
||||
*this, this->get_pass_type(render_pass), compositor::ResultPrecision::Full);
|
||||
if (pass.type() != pass_data.type()) {
|
||||
compositor::ConversionOperation conversion_operation(*this, pass_data.type(), pass.type());
|
||||
conversion_operation.map_input_to_result(&pass_data);
|
||||
conversion_operation.evaluate();
|
||||
pass.share_data(conversion_operation.get_result());
|
||||
conversion_operation.get_result().release();
|
||||
}
|
||||
else {
|
||||
pass.share_data(pass_data);
|
||||
pass_data.release();
|
||||
}
|
||||
|
||||
/* We assume the given pass is a Cryptomatte pass and retrieve its layer name. If it wasn't a
|
||||
* Cryptomatte pass, the checks below will fail anyway. */
|
||||
const std::string combined_pass_name = std::string(view_layer->name) + "." + pass_name;
|
||||
StringRef cryptomatte_layer_name = bke::cryptomatte::BKE_cryptomatte_extract_layer_name(
|
||||
combined_pass_name);
|
||||
|
||||
struct StampCallbackData {
|
||||
std::string cryptomatte_layer_name;
|
||||
compositor::MetaData *meta_data;
|
||||
};
|
||||
|
||||
/* Go over the stamp data and add any Cryptomatte related meta data. */
|
||||
StampCallbackData callback_data = {cryptomatte_layer_name, &pass.meta_data};
|
||||
BKE_stamp_info_callback(
|
||||
&callback_data,
|
||||
render_result->stamp_data,
|
||||
[](void *user_data, const char *key, char *value, int /*value_maxncpy*/) {
|
||||
StampCallbackData *data = static_cast<StampCallbackData *>(user_data);
|
||||
|
||||
const std::string manifest_key = bke::cryptomatte::BKE_cryptomatte_meta_data_key(
|
||||
data->cryptomatte_layer_name, "manifest");
|
||||
if (key == manifest_key) {
|
||||
data->meta_data->cryptomatte.manifest = value;
|
||||
}
|
||||
|
||||
const std::string hash_key = bke::cryptomatte::BKE_cryptomatte_meta_data_key(
|
||||
data->cryptomatte_layer_name, "hash");
|
||||
if (key == hash_key) {
|
||||
data->meta_data->cryptomatte.hash = value;
|
||||
}
|
||||
|
||||
const std::string conversion_key = bke::cryptomatte::BKE_cryptomatte_meta_data_key(
|
||||
data->cryptomatte_layer_name, "conversion");
|
||||
if (key == conversion_key) {
|
||||
data->meta_data->cryptomatte.conversion = value;
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
StringRef get_view_name() const override
|
||||
{
|
||||
return input_data_.view_name;
|
||||
}
|
||||
|
||||
compositor::ResultPrecision get_precision() const override
|
||||
{
|
||||
switch (input_data_.scene->r.compositor_precision) {
|
||||
case SCE_COMPOSITOR_PRECISION_AUTO:
|
||||
/* Auto uses full precision for final renders and half precision otherwise. */
|
||||
if (this->render_context()) {
|
||||
return compositor::ResultPrecision::Full;
|
||||
}
|
||||
else {
|
||||
return compositor::ResultPrecision::Half;
|
||||
}
|
||||
case SCE_COMPOSITOR_PRECISION_FULL:
|
||||
return compositor::ResultPrecision::Full;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return compositor::ResultPrecision::Full;
|
||||
}
|
||||
|
||||
compositor::RenderContext *render_context() const override
|
||||
{
|
||||
return input_data_.render_context;
|
||||
}
|
||||
|
||||
nodes::eval_log::NodesEvalLog *nodes_evaluation_log() const override
|
||||
{
|
||||
return this->get_scene().runtime->compositor.nodes_evaluation_log.get();
|
||||
}
|
||||
|
||||
void evaluate_operation_post() const override
|
||||
{
|
||||
/* If no render context exist, that means this is an interactive compositor evaluation due to
|
||||
* the user editing the node tree. In that case, we wait until the operation finishes executing
|
||||
* on the GPU before we continue to improve interactivity. The improvement comes from the fact
|
||||
* that the user might be rapidly changing values, so we need to cancel previous evaluations to
|
||||
* make editing faster, but we can't do that if all operations are submitted to the GPU all at
|
||||
* once, and we can't cancel work that was already submitted to the GPU. This does have a
|
||||
* performance penalty, but in practice, the improved interactivity is worth it according to
|
||||
* user feedback. */
|
||||
if (this->use_gpu() && !this->render_context()) {
|
||||
GPU_finish();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_canceled() const override
|
||||
{
|
||||
return input_data_.render->display->test_break();
|
||||
}
|
||||
|
||||
void evaluate()
|
||||
{
|
||||
/* Reset log before evaluation. */
|
||||
this->get_scene().runtime->compositor.nodes_evaluation_log =
|
||||
std::make_unique<nodes::eval_log::NodesEvalLog>();
|
||||
|
||||
using namespace compositor;
|
||||
const NodeGroupOutputTypes needed_outputs = this->needed_outputs();
|
||||
const bNodeTree &node_group = *input_data_.node_tree;
|
||||
const bke::DataBlockComputeContext compute_context(nullptr, this->get_scene().id);
|
||||
NodeGroupOperation node_group_operation(*this,
|
||||
node_group,
|
||||
needed_outputs,
|
||||
node_group.active_viewer_key,
|
||||
bke::NODE_INSTANCE_KEY_BASE,
|
||||
compute_context);
|
||||
|
||||
/* Set the reference count for the outputs, only the first color output is actually needed,
|
||||
* while the rest are ignored. */
|
||||
const bool is_group_output_needed = flag_is_set(needed_outputs,
|
||||
NodeGroupOutputTypes::GroupOutputNode);
|
||||
node_group.ensure_interface_cache();
|
||||
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
|
||||
const bool is_first_output = output_socket == node_group.interface_outputs().first();
|
||||
Result &output_result = node_group_operation.get_result(output_socket->identifier);
|
||||
const bool is_color = output_result.type() == ResultType::Color;
|
||||
const bool is_needed = is_group_output_needed && is_first_output && is_color;
|
||||
output_result.set_reference_count(is_needed ? 1 : 0);
|
||||
}
|
||||
|
||||
/* Map the inputs to the operation. */
|
||||
Vector<std::unique_ptr<Result>> inputs;
|
||||
for (const bNodeTreeInterfaceSocket *input_socket : node_group.interface_inputs()) {
|
||||
Result *input_result = new Result(
|
||||
this->create_result(ResultType::Color, ResultPrecision::Full));
|
||||
if (input_socket == node_group.interface_inputs()[0]) {
|
||||
/* First socket is the combined pass. */
|
||||
Result combined_pass = this->get_pass(&this->get_scene(), 0, "Image");
|
||||
if (combined_pass.is_allocated()) {
|
||||
input_result->share_data(combined_pass);
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
combined_pass.release();
|
||||
}
|
||||
else {
|
||||
/* The rest of the sockets are not supported. */
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
|
||||
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
|
||||
inputs.append(std::unique_ptr<Result>(input_result));
|
||||
}
|
||||
|
||||
node_group_operation.evaluate();
|
||||
|
||||
/* Write the outputs of the operation. */
|
||||
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
|
||||
Result &output_result = node_group_operation.get_result(output_socket->identifier);
|
||||
if (!output_result.should_compute()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this->is_canceled()) {
|
||||
output_result.release();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Realize the output on the compositing domain if needed. */
|
||||
const Domain compositing_domain = this->get_compositing_domain();
|
||||
const InputDescriptor input_descriptor = {ResultType::Color,
|
||||
InputRealizationMode::OperationDomain};
|
||||
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
|
||||
*this, output_result, input_descriptor, compositing_domain);
|
||||
if (realization_operation) {
|
||||
realization_operation->map_input_to_result(&output_result);
|
||||
realization_operation->evaluate();
|
||||
Result &realized_output_result = realization_operation->get_result();
|
||||
this->write_output(realized_output_result);
|
||||
realized_output_result.release();
|
||||
delete realization_operation;
|
||||
continue;
|
||||
}
|
||||
|
||||
this->write_output(output_result);
|
||||
output_result.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Render Compositor */
|
||||
|
||||
class Compositor {
|
||||
private:
|
||||
/* Render instance for GPU context to run compositor in. */
|
||||
Render &render_;
|
||||
|
||||
compositor::StaticCacheManager cache_manager_;
|
||||
|
||||
/* Stores the execution device and precision used in the last evaluation of the compositor. Those
|
||||
* might be different from the current values returned by the context, since the user might have
|
||||
* changed them since the last evaluation. See the needs_to_be_recreated method for more info on
|
||||
* why those are needed. */
|
||||
bool last_evaluation_used_gpu_ = false;
|
||||
compositor::ResultPrecision last_evaluation_precision_ = compositor::ResultPrecision::Half;
|
||||
|
||||
public:
|
||||
Compositor(Render &render) : render_(render) {}
|
||||
|
||||
~Compositor()
|
||||
{
|
||||
/* Use last_evaluation_used_gpu_ instead of the currently used device because we are freeing
|
||||
* resources from the last evaluation. See last_evaluation_used_gpu_ for more information. */
|
||||
if (last_evaluation_used_gpu_) {
|
||||
/* Free resources with GPU context enabled. Cleanup may happen from the main thread, and we
|
||||
* must use the main context there. */
|
||||
if (BLI_thread_is_main()) {
|
||||
DRW_gpu_context_enable();
|
||||
}
|
||||
else {
|
||||
DRW_render_context_enable(&render_);
|
||||
}
|
||||
}
|
||||
|
||||
cache_manager_.free();
|
||||
|
||||
/* See comment above on context enabling. */
|
||||
if (last_evaluation_used_gpu_) {
|
||||
if (BLI_thread_is_main()) {
|
||||
DRW_gpu_context_disable();
|
||||
}
|
||||
else {
|
||||
DRW_render_context_disable(&render_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void execute(const ContextInputData &input_data)
|
||||
{
|
||||
Context context(cache_manager_, input_data);
|
||||
|
||||
if (context.use_gpu()) {
|
||||
/* For main thread rendering in background mode, blocking rendering, or when we do not have a
|
||||
* render system GPU context, use the DRW context directly, while for threaded rendering when
|
||||
* we have a render system GPU context, use the render's system GPU context to avoid blocking
|
||||
* with the global DST. */
|
||||
GHOST_IContext *re_system_gpu_context = RE_system_gpu_context_get(&render_);
|
||||
if (BLI_thread_is_main() || re_system_gpu_context == nullptr) {
|
||||
DRW_gpu_context_enable();
|
||||
context.set_gpu_supported(DRW_gpu_context_is_enabled());
|
||||
}
|
||||
else if (re_system_gpu_context) {
|
||||
WM_system_gpu_context_activate(re_system_gpu_context);
|
||||
|
||||
void *re_blender_gpu_context = RE_blender_gpu_context_ensure(&render_);
|
||||
|
||||
GPU_render_begin();
|
||||
GPU_context_active_set(static_cast<GPUContext *>(re_blender_gpu_context));
|
||||
}
|
||||
else {
|
||||
context.set_gpu_supported(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
context.evaluate();
|
||||
|
||||
/* Reset the cache, but only if the evaluation did not get canceled, because in that case, we
|
||||
* wouldn't want to invalidate the cache because not all operations that use cached resources
|
||||
* got the chance to mark their used resources as still in use. So we wait until a full
|
||||
* evaluation happen before we decide that some resources are no longer needed. */
|
||||
if (!context.is_canceled()) {
|
||||
context.cache_manager().reset();
|
||||
}
|
||||
|
||||
last_evaluation_used_gpu_ = context.use_gpu();
|
||||
last_evaluation_precision_ = context.get_precision();
|
||||
}
|
||||
|
||||
if (context.use_gpu()) {
|
||||
gpu::TexturePool::get().reset();
|
||||
|
||||
GHOST_IContext *re_system_gpu_context = RE_system_gpu_context_get(&render_);
|
||||
if (BLI_thread_is_main() || re_system_gpu_context == nullptr) {
|
||||
DRW_gpu_context_disable();
|
||||
}
|
||||
else {
|
||||
GPU_context_active_set(nullptr);
|
||||
GPU_render_end();
|
||||
GHOST_IContext *re_system_gpu_context = RE_system_gpu_context_get(&render_);
|
||||
WM_system_gpu_context_release(re_system_gpu_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns true if the compositor should be freed and reconstructed, which is needed when the
|
||||
* compositor execution device or precision changed, because we either need to update all cached
|
||||
* resources for the new execution device and precision, or we simply recreate the entire
|
||||
* compositor, since it is much easier and safer. */
|
||||
bool needs_to_be_recreated(const ContextInputData &input_data)
|
||||
{
|
||||
Context context(cache_manager_, input_data);
|
||||
/* See last_evaluation_used_gpu_ and last_evaluation_precision_ for more information what how
|
||||
* they are different from the ones returned from the context. */
|
||||
return context.use_gpu() != last_evaluation_used_gpu_ ||
|
||||
context.get_precision() != last_evaluation_precision_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
|
||||
void Render::compositor_execute(const Main &main,
|
||||
const Scene &scene,
|
||||
const RenderData &render_data,
|
||||
const bNodeTree &node_tree,
|
||||
const char *view_name,
|
||||
compositor::RenderContext *render_context,
|
||||
compositor::NodeGroupOutputTypes needed_outputs)
|
||||
{
|
||||
std::unique_lock lock(this->compositor_mutex);
|
||||
|
||||
render::ContextInputData input_data(
|
||||
this, main, scene, render_data, node_tree, view_name, render_context, needed_outputs);
|
||||
|
||||
if (this->compositor && this->compositor->needs_to_be_recreated(input_data)) {
|
||||
/* Free it here and it will be recreated in the check below. */
|
||||
delete this->compositor;
|
||||
this->compositor = nullptr;
|
||||
}
|
||||
|
||||
if (!this->compositor) {
|
||||
this->compositor = new render::Compositor(*this);
|
||||
}
|
||||
|
||||
this->compositor->execute(input_data);
|
||||
}
|
||||
|
||||
void Render::compositor_free()
|
||||
{
|
||||
std::unique_lock lock(this->compositor_mutex);
|
||||
|
||||
if (this->compositor != nullptr) {
|
||||
delete this->compositor;
|
||||
this->compositor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RE_compositor_execute(Render &render,
|
||||
const Main &main,
|
||||
const Scene &scene,
|
||||
const RenderData &render_data,
|
||||
const bNodeTree &node_tree,
|
||||
const char *view_name,
|
||||
compositor::RenderContext *render_context,
|
||||
compositor::NodeGroupOutputTypes needed_outputs)
|
||||
{
|
||||
render.compositor_execute(
|
||||
main, scene, render_data, node_tree, view_name, render_context, needed_outputs);
|
||||
}
|
||||
|
||||
void RE_compositor_free(Render &render)
|
||||
{
|
||||
render.compositor_free();
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
1449
blender-5.2.0/source/blender/render/intern/engine.cc
Normal file
1449
blender-5.2.0/source/blender/render/intern/engine.cc
Normal file
File diff suppressed because it is too large
Load Diff
115
blender-5.2.0/source/blender/render/intern/initrender.cc
Normal file
115
blender-5.2.0/source/blender/render/intern/initrender.cc
Normal file
@@ -0,0 +1,115 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
/* Global includes */
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_camera.h"
|
||||
|
||||
/* this module */
|
||||
#include "RE_pipeline.h"
|
||||
#include "render_types.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
Object *RE_GetCamera(Render *re)
|
||||
{
|
||||
Object *camera = re->camera_override ? re->camera_override : re->scene->camera;
|
||||
return BKE_camera_multiview_render(*re->main, re->scene, camera, re->viewname);
|
||||
}
|
||||
|
||||
void RE_SetOverrideCamera(Render *re, Object *cam_ob)
|
||||
{
|
||||
re->camera_override = cam_ob;
|
||||
}
|
||||
|
||||
void RE_SetCamera(Render *re, const Object *cam_ob)
|
||||
{
|
||||
CameraParams params;
|
||||
|
||||
/* setup parameters */
|
||||
BKE_camera_params_init(¶ms);
|
||||
BKE_camera_params_from_object(¶ms, cam_ob);
|
||||
BKE_camera_multiview_params(&re->r, ¶ms, cam_ob, re->viewname);
|
||||
|
||||
/* Compute matrix, view-plane, etc. */
|
||||
BKE_camera_params_compute_viewplane(¶ms, re->winx, re->winy, re->r.xasp, re->r.yasp);
|
||||
BKE_camera_params_compute_matrix(¶ms);
|
||||
|
||||
/* extract results */
|
||||
copy_m4_m4(re->winmat, params.winmat);
|
||||
re->clip_start = params.clip_start;
|
||||
re->clip_end = params.clip_end;
|
||||
re->viewplane = params.viewplane;
|
||||
}
|
||||
|
||||
void RE_GetCameraWindow(Render *re, const Object *camera, float r_winmat[4][4])
|
||||
{
|
||||
RE_SetCamera(re, camera);
|
||||
copy_m4_m4(r_winmat, re->winmat);
|
||||
}
|
||||
|
||||
void RE_GetCameraWindowWithOverscan(const Render *re, float overscan, float r_winmat[4][4])
|
||||
{
|
||||
RE_GetWindowMatrixWithOverscan(
|
||||
re->winmat[3][3] != 0.0f, re->clip_start, re->clip_end, re->viewplane, overscan, r_winmat);
|
||||
}
|
||||
|
||||
void RE_GetCameraModelMatrix(const Render *re, const Object *camera, float r_modelmat[4][4])
|
||||
{
|
||||
BKE_camera_multiview_model_matrix(&re->r, camera, re->viewname, r_modelmat);
|
||||
}
|
||||
|
||||
void RE_GetWindowMatrixWithOverscan(bool is_ortho,
|
||||
float clip_start,
|
||||
float clip_end,
|
||||
rctf viewplane,
|
||||
float overscan,
|
||||
float r_winmat[4][4])
|
||||
{
|
||||
CameraParams params;
|
||||
params.is_ortho = is_ortho;
|
||||
params.clip_start = clip_start;
|
||||
params.clip_end = clip_end;
|
||||
params.viewplane = viewplane;
|
||||
|
||||
overscan *= max_ff(BLI_rctf_size_x(¶ms.viewplane), BLI_rctf_size_y(¶ms.viewplane));
|
||||
|
||||
params.viewplane.xmin -= overscan;
|
||||
params.viewplane.xmax += overscan;
|
||||
params.viewplane.ymin -= overscan;
|
||||
params.viewplane.ymax += overscan;
|
||||
BKE_camera_params_compute_matrix(¶ms);
|
||||
copy_m4_m4(r_winmat, params.winmat);
|
||||
}
|
||||
|
||||
void RE_GetViewPlane(Render *re, rctf *r_viewplane, rcti *r_disprect)
|
||||
{
|
||||
*r_viewplane = re->viewplane;
|
||||
|
||||
/* make disprect zero when no border render, is needed to detect changes in 3d view render */
|
||||
if (re->r.mode & R_BORDER) {
|
||||
*r_disprect = re->disprect;
|
||||
}
|
||||
else {
|
||||
BLI_rcti_init(r_disprect, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
1734
blender-5.2.0/source/blender/render/intern/multires_bake.cc
Normal file
1734
blender-5.2.0/source/blender/render/intern/multires_bake.cc
Normal file
File diff suppressed because it is too large
Load Diff
2849
blender-5.2.0/source/blender/render/intern/pipeline.cc
Normal file
2849
blender-5.2.0/source/blender/render/intern/pipeline.cc
Normal file
File diff suppressed because it is too large
Load Diff
21
blender-5.2.0/source/blender/render/intern/pipeline.hh
Normal file
21
blender-5.2.0/source/blender/render/intern/pipeline.hh
Normal file
@@ -0,0 +1,21 @@
|
||||
/* SPDX-FileCopyrightText: 2006 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Render;
|
||||
struct RenderData;
|
||||
struct RenderLayer;
|
||||
struct RenderResult;
|
||||
|
||||
RenderLayer *render_get_single_layer(Render *re, RenderResult *rr);
|
||||
void render_copy_renderdata(RenderData *to, RenderData *from);
|
||||
|
||||
} // namespace blender
|
||||
1362
blender-5.2.0/source/blender/render/intern/render_result.cc
Normal file
1362
blender-5.2.0/source/blender/render/intern/render_result.cc
Normal file
File diff suppressed because it is too large
Load Diff
163
blender-5.2.0/source/blender/render/intern/render_result.h
Normal file
163
blender-5.2.0/source/blender/render/intern/render_result.h
Normal file
@@ -0,0 +1,163 @@
|
||||
/* SPDX-FileCopyrightText: 2007 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_global.hh" /* IWYU pragma: keep. Used in macro. */
|
||||
|
||||
namespace blender {
|
||||
|
||||
#define PASS_VECTOR_MAX 10000.0f
|
||||
|
||||
#define RR_ALL_LAYERS NULL
|
||||
#define RR_ALL_VIEWS NULL
|
||||
|
||||
struct ColorManagedDisplaySettings;
|
||||
struct ColorManagedViewSettings;
|
||||
struct ExrHandle;
|
||||
struct ImBuf;
|
||||
struct Render;
|
||||
struct RenderData;
|
||||
struct RenderLayer;
|
||||
struct RenderResult;
|
||||
struct ReportList;
|
||||
struct rcti;
|
||||
|
||||
/* New */
|
||||
|
||||
/**
|
||||
* Called by main render as well for parts will read info from Render *re to define layers.
|
||||
* \note Called in threads.
|
||||
*
|
||||
* `re->winx`, `re->winy` is coordinate space of entire image, `partrct` the part within.
|
||||
*/
|
||||
struct RenderResult *render_result_new(struct Render *re,
|
||||
const struct rcti *partrct,
|
||||
const char *layername,
|
||||
const char *viewname);
|
||||
|
||||
void render_result_passes_allocated_ensure(struct RenderResult *rr);
|
||||
|
||||
/**
|
||||
* From `imbuf`, if a handle was returned and
|
||||
* it's not a single-layer multi-view we convert this to render result.
|
||||
*/
|
||||
struct RenderResult *render_result_new_from_exr(
|
||||
ExrHandle *exrhandle, const char *colorspace, bool predivide, int rectx, int recty);
|
||||
|
||||
void render_result_view_new(struct RenderResult *rr, const char *viewname);
|
||||
void render_result_views_new(struct RenderResult *rr, const struct RenderData *rd);
|
||||
|
||||
/* Merge */
|
||||
|
||||
/**
|
||||
* Used when rendering to a full buffer, or when reading the EXR part-layer-pass file.
|
||||
* no test happens here if it fits... we also assume layers are in sync.
|
||||
* \note Is used within threads.
|
||||
*/
|
||||
void render_result_merge(struct RenderResult *rr, struct RenderResult *rrpart);
|
||||
|
||||
/* Add Passes */
|
||||
|
||||
void render_result_clone_passes(struct Render *re, struct RenderResult *rr, const char *viewname);
|
||||
|
||||
/* Free */
|
||||
|
||||
void render_result_free(struct RenderResult *rr);
|
||||
/**
|
||||
* Version that's compatible with full-sample buffers.
|
||||
*/
|
||||
void render_result_free_list(ListBaseT<RenderResult> *lb, struct RenderResult *rr);
|
||||
|
||||
/* Single Layer Render */
|
||||
|
||||
void render_result_single_layer_begin(struct Render *re);
|
||||
/**
|
||||
* If #RenderData.scemode is #R_SINGLE_LAYER, at end of rendering, merge the both render results.
|
||||
*/
|
||||
void render_result_single_layer_end(struct Render *re);
|
||||
|
||||
/**
|
||||
* Render pass wrapper for grease-pencil.
|
||||
*/
|
||||
struct RenderPass *render_layer_add_pass(struct RenderResult *rr,
|
||||
struct RenderLayer *rl,
|
||||
int channels,
|
||||
const char *name,
|
||||
const char *viewname,
|
||||
const char *chan_id,
|
||||
bool allocate);
|
||||
|
||||
/**
|
||||
* Called for reading temp files, and for external engines.
|
||||
*/
|
||||
bool render_result_exr_file_read_path(struct RenderResult *rr,
|
||||
struct RenderLayer *rl_single,
|
||||
struct ReportList *reports,
|
||||
const char *filepath);
|
||||
|
||||
/* EXR cache */
|
||||
|
||||
void render_result_exr_file_cache_write(struct Render *re);
|
||||
/**
|
||||
* For cache, makes exact copy of render result.
|
||||
*/
|
||||
bool render_result_exr_file_cache_read(struct Render *re);
|
||||
|
||||
/* Combined Pixel Rect */
|
||||
|
||||
struct ImBuf *render_result_rect_to_ibuf(struct RenderResult *rr,
|
||||
const struct RenderData *rd,
|
||||
int view_id);
|
||||
|
||||
void render_result_rect_fill_zero(struct RenderResult *rr, int view_id);
|
||||
void render_result_rect_get_pixels(struct RenderResult *rr,
|
||||
uint8_t *rect,
|
||||
int rectx,
|
||||
int recty,
|
||||
const struct ColorManagedViewSettings *view_settings,
|
||||
const struct ColorManagedDisplaySettings *display_settings,
|
||||
int view_id);
|
||||
|
||||
/**
|
||||
* Create a new views #ListBaseT in rr without duplicating the memory pointers.
|
||||
*/
|
||||
void render_result_views_shallowcopy(struct RenderResult *dst, struct RenderResult *src);
|
||||
/**
|
||||
* Free the views created temporarily.
|
||||
*/
|
||||
void render_result_views_shallowdelete(struct RenderResult *rr);
|
||||
|
||||
/**
|
||||
* Free GPU texture caches to reduce memory usage.
|
||||
*/
|
||||
void render_result_free_gpu_texture_caches(struct RenderResult *rr);
|
||||
|
||||
#define FOREACH_VIEW_LAYER_TO_RENDER_BEGIN(re_, iter_) \
|
||||
{ \
|
||||
ViewLayer *iter_; \
|
||||
for (iter_ = static_cast<ViewLayer *>((re_)->scene->view_layers.first); iter_ != NULL; \
|
||||
iter_ = iter_->next) \
|
||||
{ \
|
||||
if (!G.background && (re_)->r.scemode & R_SINGLE_LAYER) { \
|
||||
if (!STREQ(iter_->name, re->single_view_layer)) { \
|
||||
continue; \
|
||||
} \
|
||||
} \
|
||||
else { \
|
||||
if ((iter_->flag & VIEW_LAYER_RENDER) == 0) { \
|
||||
continue; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define FOREACH_VIEW_LAYER_TO_RENDER_END \
|
||||
} \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
} // namespace blender
|
||||
185
blender-5.2.0/source/blender/render/intern/render_types.cc
Normal file
185
blender-5.2.0/source/blender/render/intern/render_types.cc
Normal file
@@ -0,0 +1,185 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#include "render_types.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "RE_compositor.hh"
|
||||
#include "RE_engine.h"
|
||||
|
||||
#include "render_result.h"
|
||||
|
||||
#include "GPU_context.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "wm_window.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Render
|
||||
* \{ */
|
||||
|
||||
BaseRender::~BaseRender()
|
||||
{
|
||||
if (engine) {
|
||||
RE_engine_free(engine);
|
||||
}
|
||||
|
||||
render_result_free(result);
|
||||
|
||||
/* Free GPU context after engine, which may need context for cleanup. */
|
||||
display.reset();
|
||||
|
||||
BLI_rw_mutex_end(&resultmutex);
|
||||
BLI_mutex_end(&engine_draw_mutex);
|
||||
}
|
||||
|
||||
Render::Render()
|
||||
{
|
||||
display = std::make_shared<RenderDisplay>();
|
||||
}
|
||||
|
||||
Render::~Render()
|
||||
{
|
||||
RE_compositor_free(*this);
|
||||
|
||||
BKE_curvemapping_free_data(&r.mblur_shutter_curve);
|
||||
|
||||
render_result_free(pushedresult);
|
||||
}
|
||||
|
||||
bool Render::prepare_viewlayer(ViewLayer *view_layer, Depsgraph *depsgraph)
|
||||
{
|
||||
if (!prepare_viewlayer_cb) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return prepare_viewlayer_cb(prepare_vl_handle, view_layer, depsgraph);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name RenderDisplay
|
||||
* \{ */
|
||||
|
||||
RenderDisplay::~RenderDisplay()
|
||||
{
|
||||
free_gpu_context();
|
||||
|
||||
display_update_cb = nullptr;
|
||||
current_scene_update_cb = nullptr;
|
||||
stats_draw_cb = nullptr;
|
||||
progress_cb = nullptr;
|
||||
draw_lock_cb = nullptr;
|
||||
test_break_cb = nullptr;
|
||||
}
|
||||
|
||||
void RenderDisplay::free_gpu_context()
|
||||
{
|
||||
if (blender_gpu_context) {
|
||||
WM_system_gpu_context_activate(system_gpu_context);
|
||||
GPU_context_active_set(static_cast<GPUContext *>(blender_gpu_context));
|
||||
GPU_context_discard(static_cast<GPUContext *>(blender_gpu_context));
|
||||
blender_gpu_context = nullptr;
|
||||
}
|
||||
|
||||
if (system_gpu_context) {
|
||||
WM_system_gpu_context_dispose(system_gpu_context);
|
||||
system_gpu_context = nullptr;
|
||||
|
||||
/* If in main thread, reset window context. */
|
||||
if (BLI_thread_is_main()) {
|
||||
wm_window_reset_drawable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDisplay::ensure_system_gpu_context()
|
||||
{
|
||||
BLI_assert(BLI_thread_is_main());
|
||||
|
||||
if (system_gpu_context == nullptr) {
|
||||
/* Needs to be created in the main thread. */
|
||||
system_gpu_context = WM_system_gpu_context_create();
|
||||
/* The context is activated during creation, so release it here since the function should not
|
||||
* have context activation as a side effect. Then activate the drawable's context below. */
|
||||
if (system_gpu_context) {
|
||||
WM_system_gpu_context_release(system_gpu_context);
|
||||
}
|
||||
wm_window_reset_drawable();
|
||||
}
|
||||
}
|
||||
|
||||
void *RenderDisplay::ensure_blender_gpu_context()
|
||||
{
|
||||
BLI_assert(system_gpu_context != nullptr);
|
||||
if (blender_gpu_context == nullptr) {
|
||||
blender_gpu_context = GPU_context_create(nullptr, system_gpu_context);
|
||||
}
|
||||
return blender_gpu_context;
|
||||
}
|
||||
|
||||
void RenderDisplay::display_update(RenderResult *render_result, rcti *rect)
|
||||
{
|
||||
if (display_update_cb) {
|
||||
display_update_cb(duh, render_result, rect);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDisplay::current_scene_update(Scene *scene)
|
||||
{
|
||||
if (current_scene_update_cb) {
|
||||
current_scene_update_cb(suh, scene);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDisplay::stats_draw(RenderStats *render_stats)
|
||||
{
|
||||
if (stats_draw_cb) {
|
||||
stats_draw_cb(sdh, render_stats);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDisplay::progress(float progress)
|
||||
{
|
||||
if (progress_cb) {
|
||||
progress_cb(prh, progress);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDisplay::draw_lock()
|
||||
{
|
||||
if (draw_lock_cb) {
|
||||
draw_lock_cb(dlh, true);
|
||||
}
|
||||
}
|
||||
void RenderDisplay::draw_unlock()
|
||||
{
|
||||
if (draw_lock_cb) {
|
||||
draw_lock_cb(dlh, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderDisplay::test_break()
|
||||
{
|
||||
if (!test_break_cb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return test_break_cb(tbh);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender
|
||||
258
blender-5.2.0/source/blender/render/intern/render_types.h
Normal file
258
blender-5.2.0/source/blender/render/intern/render_types.h
Normal file
@@ -0,0 +1,258 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* exposed internal in render module only! */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BLI_mutex.hh"
|
||||
#include "BLI_threads.h"
|
||||
|
||||
#include "RE_compositor.hh"
|
||||
#include "RE_pipeline.h"
|
||||
|
||||
#include "tile_highlight.h"
|
||||
|
||||
class GHOST_IContext;
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace compositor {
|
||||
class RenderContext;
|
||||
enum class NodeGroupOutputTypes : uint8_t;
|
||||
} // namespace compositor
|
||||
|
||||
struct bNodeTree;
|
||||
struct Depsgraph;
|
||||
struct Main;
|
||||
struct Object;
|
||||
struct RenderDisplay;
|
||||
struct RenderEngine;
|
||||
struct ReportList;
|
||||
struct Scene;
|
||||
|
||||
struct BaseRender {
|
||||
BaseRender() = default;
|
||||
virtual ~BaseRender();
|
||||
|
||||
/* Get class which manages highlight of tiles.
|
||||
* Note that it might not exist: for example, viewport render does not support the tile
|
||||
* highlight. */
|
||||
virtual render::TilesHighlight *get_tile_highlight() = 0;
|
||||
|
||||
virtual void compositor_execute(const Main &main,
|
||||
const Scene &scene,
|
||||
const RenderData &render_data,
|
||||
const bNodeTree &node_tree,
|
||||
const char *view_name,
|
||||
compositor::RenderContext *render_context,
|
||||
compositor::NodeGroupOutputTypes needed_outputs) = 0;
|
||||
virtual void compositor_free() = 0;
|
||||
|
||||
/**
|
||||
* Executed right before the initialization of the depsgraph, in order to modify some stuff in
|
||||
* the viewlayer. The modified ids must be tagged in the depsgraph.
|
||||
*
|
||||
* If false is returned then rendering is aborted,
|
||||
*/
|
||||
virtual bool prepare_viewlayer(struct ViewLayer *view_layer, struct Depsgraph *depsgraph) = 0;
|
||||
|
||||
/* Result of rendering */
|
||||
RenderResult *result = nullptr;
|
||||
|
||||
/* Read/write mutex, all internal code that writes to the `result` must use a
|
||||
* write lock, all external code must use a read lock. Internal code is assumed
|
||||
* to not conflict with writes, so no lock used for that. */
|
||||
ThreadRWMutex resultmutex = BLI_RWLOCK_INITIALIZER;
|
||||
|
||||
/* Render engine. */
|
||||
struct RenderEngine *engine = nullptr;
|
||||
|
||||
/* Guard for drawing render result using engine's `draw()` callback. */
|
||||
ThreadMutex engine_draw_mutex = BLI_MUTEX_INITIALIZER;
|
||||
|
||||
/**
|
||||
* GPU context and callbacks. This can be shared for recursive compositor and
|
||||
* sequencer renders that we want to display in the same place.
|
||||
*/
|
||||
std::shared_ptr<RenderDisplay> display;
|
||||
bool display_shared = false;
|
||||
};
|
||||
|
||||
struct ViewRender : public BaseRender {
|
||||
render::TilesHighlight *get_tile_highlight() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void compositor_execute(const Main & /*main*/,
|
||||
const Scene & /*scene*/,
|
||||
const RenderData & /*render_data*/,
|
||||
const bNodeTree & /*node_tree*/,
|
||||
const char * /*view_name*/,
|
||||
compositor::RenderContext * /*render_context*/,
|
||||
compositor::NodeGroupOutputTypes /*needed_outputs*/) override
|
||||
{
|
||||
}
|
||||
void compositor_free() override {}
|
||||
|
||||
bool prepare_viewlayer(struct ViewLayer * /*view_layer*/,
|
||||
struct Depsgraph * /*depsgraph*/) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** Controls state of render, everything that's read-only during render stage. */
|
||||
struct Render : public BaseRender {
|
||||
Render();
|
||||
~Render() override;
|
||||
|
||||
render::TilesHighlight *get_tile_highlight() override
|
||||
{
|
||||
return &tile_highlight;
|
||||
}
|
||||
|
||||
void compositor_execute(const Main &main,
|
||||
const Scene &scene,
|
||||
const RenderData &render_data,
|
||||
const bNodeTree &node_tree,
|
||||
const char *view_name,
|
||||
compositor::RenderContext *render_context,
|
||||
compositor::NodeGroupOutputTypes needed_outputs) override;
|
||||
void compositor_free() override;
|
||||
|
||||
bool prepare_viewlayer(struct ViewLayer *view_layer, struct Depsgraph *depsgraph) override;
|
||||
|
||||
/* Owner pointer that uniquely identifiers the owner of this scene. */
|
||||
const void *owner = nullptr;
|
||||
|
||||
/* state settings */
|
||||
short flag = 0;
|
||||
bool ok = false;
|
||||
|
||||
/* if render with single-layer option, other rendered layers are stored here */
|
||||
RenderResult *pushedresult = nullptr;
|
||||
/** A list of #RenderResults, for full-samples. */
|
||||
ListBaseT<RenderResult> fullresult = {nullptr, nullptr};
|
||||
/* True if result has GPU textures, to quickly skip cache clear. */
|
||||
bool result_has_gpu_texture_caches = false;
|
||||
|
||||
/** Window size, display rect, viewplane.
|
||||
* \note Buffer width and height with percentage applied
|
||||
* without border & crop. convert to long before multiplying together to avoid overflow. */
|
||||
int winx = 0, winy = 0;
|
||||
rcti disprect = {0, 0, 0, 0}; /* part within winx winy */
|
||||
rctf viewplane = {0, 0, 0, 0}; /* mapped on winx winy */
|
||||
|
||||
/* final picture width and height (within disprect) */
|
||||
int rectx = 0, recty = 0;
|
||||
|
||||
/* Camera transform. Used by Freestyle, Eevee, and other draw manager engines.. */
|
||||
float winmat[4][4] = {{0}};
|
||||
|
||||
/* Clipping. */
|
||||
float clip_start = 0.0f;
|
||||
float clip_end = 0.0f;
|
||||
|
||||
/* main, scene, and its full copy of renderdata and world */
|
||||
struct Main *main = nullptr;
|
||||
Scene *scene = nullptr;
|
||||
RenderData r = {};
|
||||
char single_view_layer[MAX_NAME] = "";
|
||||
struct Object *camera_override = nullptr;
|
||||
|
||||
render::TilesHighlight tile_highlight;
|
||||
|
||||
/* NOTE: This is a minimal dependency graph and evaluated scene which is enough to access view
|
||||
* layer visibility and use for postprocessing (compositor and sequencer). */
|
||||
struct Depsgraph *pipeline_depsgraph = nullptr;
|
||||
Scene *pipeline_scene_eval = nullptr;
|
||||
|
||||
/* Compositor.
|
||||
* NOTE: Use bare pointer instead of smart pointer because the it is a fully opaque type. */
|
||||
render::Compositor *compositor = nullptr;
|
||||
Mutex compositor_mutex;
|
||||
|
||||
/* Callbacks for the corresponding base class method implementation. */
|
||||
bool (*prepare_viewlayer_cb)(void *handle,
|
||||
struct ViewLayer *vl,
|
||||
struct Depsgraph *depsgraph) = nullptr;
|
||||
void *prepare_vl_handle = nullptr;
|
||||
|
||||
RenderStats i = {};
|
||||
|
||||
/**
|
||||
* Optional report list which may be null (borrowed memory).
|
||||
* Callers to rendering functions are responsible for setting can clearing, see: #RE_SetReports.
|
||||
*/
|
||||
struct ReportList *reports = nullptr;
|
||||
|
||||
Vector<MovieWriter *> movie_writers;
|
||||
char viewname[MAX_NAME] = "";
|
||||
};
|
||||
|
||||
struct RenderDisplay {
|
||||
~RenderDisplay();
|
||||
|
||||
void free_gpu_context();
|
||||
|
||||
void ensure_system_gpu_context();
|
||||
void *ensure_blender_gpu_context();
|
||||
|
||||
void display_update(RenderResult *render_result, rcti *rect);
|
||||
void current_scene_update(struct Scene *scene);
|
||||
|
||||
void stats_draw(RenderStats *render_stats);
|
||||
void progress(float progress);
|
||||
|
||||
void draw_lock();
|
||||
void draw_unlock();
|
||||
|
||||
bool test_break();
|
||||
|
||||
/* Callbacks */
|
||||
void (*display_update_cb)(void *handle, RenderResult *rr, rcti *rect) = nullptr;
|
||||
void *duh = nullptr;
|
||||
void (*current_scene_update_cb)(void *handle, struct Scene *scene) = nullptr;
|
||||
void *suh = nullptr;
|
||||
|
||||
void (*stats_draw_cb)(void *handle, RenderStats *ri) = nullptr;
|
||||
void *sdh = nullptr;
|
||||
void (*progress_cb)(void *handle, float i) = nullptr;
|
||||
void *prh = nullptr;
|
||||
|
||||
void (*draw_lock_cb)(void *handle, bool lock) = nullptr;
|
||||
void *dlh = nullptr;
|
||||
bool (*test_break_cb)(void *handle) = nullptr;
|
||||
void *tbh = nullptr;
|
||||
|
||||
/* GPU contexts.
|
||||
* TODO: replace by a whole draw manager. */
|
||||
GHOST_IContext *system_gpu_context = nullptr;
|
||||
void *blender_gpu_context = nullptr;
|
||||
};
|
||||
|
||||
/* **************** defines ********************* */
|
||||
|
||||
/** #R.flag */
|
||||
#define R_ANIMATION 1 << 0
|
||||
/* Indicates that the render pipeline should not write its render result. This happens for instance
|
||||
* when the render pipeline uses the compositor, but the compositor node tree does not have a group
|
||||
* output node or a render layer input, and consequently no render result. In that case, the output
|
||||
* will be written from the File Output nodes, since the render pipeline will early fail if neither
|
||||
* a File Output nor a Group Output node exist in the scene. */
|
||||
#define R_SKIP_WRITE 1 << 1
|
||||
|
||||
} // namespace blender
|
||||
87
blender-5.2.0/source/blender/render/intern/texture_common.h
Normal file
87
blender-5.2.0/source/blender/render/intern/texture_common.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_color.h" /* IWYU pragma: keep. Used in macros. */
|
||||
|
||||
namespace blender {
|
||||
|
||||
#define BRICONT \
|
||||
texres->tin = (texres->tin - 0.5f) * tex->contrast + tex->bright - 0.5f; \
|
||||
if (!(tex->flag & TEX_NO_CLAMP)) { \
|
||||
if (texres->tin < 0.0f) { \
|
||||
texres->tin = 0.0f; \
|
||||
} \
|
||||
else if (texres->tin > 1.0f) { \
|
||||
texres->tin = 1.0f; \
|
||||
} \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
#define BRICONTRGB \
|
||||
texres->trgba[0] = tex->rfac * \
|
||||
((texres->trgba[0] - 0.5f) * tex->contrast + tex->bright - 0.5f); \
|
||||
texres->trgba[1] = tex->gfac * \
|
||||
((texres->trgba[1] - 0.5f) * tex->contrast + tex->bright - 0.5f); \
|
||||
texres->trgba[2] = tex->bfac * \
|
||||
((texres->trgba[2] - 0.5f) * tex->contrast + tex->bright - 0.5f); \
|
||||
if (!(tex->flag & TEX_NO_CLAMP)) { \
|
||||
if (texres->trgba[0] < 0.0f) { \
|
||||
texres->trgba[0] = 0.0f; \
|
||||
} \
|
||||
if (texres->trgba[1] < 0.0f) { \
|
||||
texres->trgba[1] = 0.0f; \
|
||||
} \
|
||||
if (texres->trgba[2] < 0.0f) { \
|
||||
texres->trgba[2] = 0.0f; \
|
||||
} \
|
||||
} \
|
||||
if (tex->saturation != 1.0f) { \
|
||||
float _hsv[3]; \
|
||||
rgb_to_hsv(texres->trgba[0], texres->trgba[1], texres->trgba[2], _hsv, _hsv + 1, _hsv + 2); \
|
||||
_hsv[1] *= tex->saturation; \
|
||||
hsv_to_rgb( \
|
||||
_hsv[0], _hsv[1], _hsv[2], &texres->trgba[0], &texres->trgba[1], &texres->trgba[2]); \
|
||||
if ((tex->saturation > 1.0f) && !(tex->flag & TEX_NO_CLAMP)) { \
|
||||
if (texres->trgba[0] < 0.0f) { \
|
||||
texres->trgba[0] = 0.0f; \
|
||||
} \
|
||||
if (texres->trgba[1] < 0.0f) { \
|
||||
texres->trgba[1] = 0.0f; \
|
||||
} \
|
||||
if (texres->trgba[2] < 0.0f) { \
|
||||
texres->trgba[2] = 0.0f; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
((void)0)
|
||||
|
||||
struct ImBuf;
|
||||
struct Image;
|
||||
struct ImagePool;
|
||||
struct Tex;
|
||||
struct TexResult;
|
||||
|
||||
/* `texture_image.cc` */
|
||||
|
||||
int imagewrap(struct Tex *tex,
|
||||
struct Image *ima,
|
||||
const float texvec[3],
|
||||
struct TexResult *texres,
|
||||
struct ImagePool *pool,
|
||||
bool skip_load_image);
|
||||
void image_sample(struct Image *ima,
|
||||
float fx,
|
||||
float fy,
|
||||
float dx,
|
||||
float dy,
|
||||
float result[4],
|
||||
struct ImagePool *pool);
|
||||
|
||||
} // namespace blender
|
||||
661
blender-5.2.0/source/blender/render/intern/texture_image.cc
Normal file
661
blender-5.2.0/source/blender/render/intern/texture_image.cc
Normal file
@@ -0,0 +1,661 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#ifndef WIN32
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# include <io.h>
|
||||
#endif
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
#include "DNA_texture_types.h"
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_rect.h"
|
||||
#include "BLI_threads.h"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BKE_image.hh"
|
||||
|
||||
#include "RE_texture.h"
|
||||
|
||||
#include "texture_common.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static void boxsample(ImBuf *ibuf,
|
||||
float minx,
|
||||
float miny,
|
||||
float maxx,
|
||||
float maxy,
|
||||
TexResult *texres,
|
||||
const short imaprepeat,
|
||||
const short imapextend);
|
||||
|
||||
/* *********** IMAGEWRAPPING ****************** */
|
||||
|
||||
/* x and y have to be checked for image size */
|
||||
static void ibuf_get_color(float col[4], const ImBuf *ibuf, int x, int y)
|
||||
{
|
||||
const int64_t ofs = int64_t(y) * ibuf->x + x;
|
||||
|
||||
if (ibuf->float_data()) {
|
||||
if (ibuf->channels == 4) {
|
||||
const float *fp = ibuf->float_data() + 4 * ofs;
|
||||
copy_v4_v4(col, fp);
|
||||
}
|
||||
else if (ibuf->channels == 3) {
|
||||
const float *fp = ibuf->float_data() + 3 * ofs;
|
||||
copy_v3_v3(col, fp);
|
||||
col[3] = 1.0f;
|
||||
}
|
||||
else {
|
||||
const float *fp = ibuf->float_data() + ofs;
|
||||
col[0] = col[1] = col[2] = col[3] = *fp;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uchar *rect = ibuf->byte_data() + 4 * ofs;
|
||||
|
||||
col[0] = float(rect[0]) * (1.0f / 255.0f);
|
||||
col[1] = float(rect[1]) * (1.0f / 255.0f);
|
||||
col[2] = float(rect[2]) * (1.0f / 255.0f);
|
||||
col[3] = float(rect[3]) * (1.0f / 255.0f);
|
||||
|
||||
/* Bytes are internally straight, however render pipeline seems to expect pre-multiplied. */
|
||||
col[0] *= col[3];
|
||||
col[1] *= col[3];
|
||||
col[2] *= col[3];
|
||||
}
|
||||
}
|
||||
|
||||
int imagewrap(Tex *tex,
|
||||
Image *ima,
|
||||
const float texvec[3],
|
||||
TexResult *texres,
|
||||
ImagePool *pool,
|
||||
const bool skip_load_image)
|
||||
{
|
||||
float fx, fy;
|
||||
int x, y, retval;
|
||||
int xi, yi; /* original values */
|
||||
|
||||
texres->tin = texres->trgba[3] = texres->trgba[0] = texres->trgba[1] = texres->trgba[2] = 0.0f;
|
||||
|
||||
retval = TEX_RGB;
|
||||
|
||||
/* quick tests */
|
||||
if (ima == nullptr) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* hack for icon render */
|
||||
if (skip_load_image && !BKE_image_has_loaded_ibuf(ima)) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
ImageUser *iuser = &tex->iuser;
|
||||
ImageUser local_iuser;
|
||||
if (ima->source == IMA_SRC_TILED) {
|
||||
/* tex->iuser might be shared by threads, so create a local copy. */
|
||||
local_iuser = tex->iuser;
|
||||
iuser = &local_iuser;
|
||||
|
||||
float new_uv[2];
|
||||
iuser->tile = BKE_image_get_tile_from_pos(ima, texvec, new_uv, nullptr);
|
||||
fx = new_uv[0];
|
||||
fy = new_uv[1];
|
||||
}
|
||||
else {
|
||||
fx = texvec[0];
|
||||
fy = texvec[1];
|
||||
}
|
||||
|
||||
ImBuf *ibuf = BKE_image_pool_acquire_ibuf(ima, iuser, pool);
|
||||
|
||||
ima->flag |= IMA_USED_FOR_RENDER;
|
||||
|
||||
if (ibuf == nullptr || (ibuf->byte_data() == nullptr && ibuf->float_data() == nullptr)) {
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* setup mapping */
|
||||
if (tex->imaflag & TEX_IMAROT) {
|
||||
std::swap(fx, fy);
|
||||
}
|
||||
|
||||
if (tex->extend == TEX_CHECKER) {
|
||||
int xs, ys;
|
||||
|
||||
xs = int(floor(fx));
|
||||
ys = int(floor(fy));
|
||||
fx -= xs;
|
||||
fy -= ys;
|
||||
|
||||
if ((tex->flag & TEX_CHECKER_ODD) == 0) {
|
||||
if ((xs + ys) & 1) {
|
||||
/* pass */
|
||||
}
|
||||
else {
|
||||
if (ima) {
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
if ((tex->flag & TEX_CHECKER_EVEN) == 0) {
|
||||
if ((xs + ys) & 1) {
|
||||
if (ima) {
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
/* scale around center, (0.5, 0.5) */
|
||||
if (tex->checkerdist < 1.0f) {
|
||||
fx = (fx - 0.5f) / (1.0f - tex->checkerdist) + 0.5f;
|
||||
fy = (fy - 0.5f) / (1.0f - tex->checkerdist) + 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
x = xi = int(floorf(fx * ibuf->x));
|
||||
y = yi = int(floorf(fy * ibuf->y));
|
||||
|
||||
if (tex->extend == TEX_CLIPCUBE) {
|
||||
if (x < 0 || y < 0 || x >= ibuf->x || y >= ibuf->y || texvec[2] < -1.0f || texvec[2] > 1.0f) {
|
||||
if (ima) {
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
else if (ELEM(tex->extend, TEX_CLIP, TEX_CHECKER)) {
|
||||
if (x < 0 || y < 0 || x >= ibuf->x || y >= ibuf->y) {
|
||||
if (ima) {
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tex->extend == TEX_EXTEND) {
|
||||
if (x >= ibuf->x) {
|
||||
x = ibuf->x - 1;
|
||||
}
|
||||
else if (x < 0) {
|
||||
x = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
x = x % ibuf->x;
|
||||
if (x < 0) {
|
||||
x += ibuf->x;
|
||||
}
|
||||
}
|
||||
if (tex->extend == TEX_EXTEND) {
|
||||
if (y >= ibuf->y) {
|
||||
y = ibuf->y - 1;
|
||||
}
|
||||
else if (y < 0) {
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
y = y % ibuf->y;
|
||||
if (y < 0) {
|
||||
y += ibuf->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep this before interpolation #29761. */
|
||||
if (ima) {
|
||||
if ((tex->imaflag & TEX_USEALPHA) && (ima->alpha_mode != IMA_ALPHA_IGNORE)) {
|
||||
if ((tex->imaflag & TEX_CALCALPHA) == 0) {
|
||||
texres->talpha = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* interpolate */
|
||||
if (tex->imaflag & TEX_INTERPOL) {
|
||||
float filterx, filtery;
|
||||
filterx = (0.5f * tex->filtersize) / ibuf->x;
|
||||
filtery = (0.5f * tex->filtersize) / ibuf->y;
|
||||
|
||||
/* Important that this value is wrapped #27782.
|
||||
* this applies the modifications made by the checks above,
|
||||
* back to the floating point values */
|
||||
fx -= float(xi - x) / float(ibuf->x);
|
||||
fy -= float(yi - y) / float(ibuf->y);
|
||||
|
||||
boxsample(ibuf,
|
||||
fx - filterx,
|
||||
fy - filtery,
|
||||
fx + filterx,
|
||||
fy + filtery,
|
||||
texres,
|
||||
(tex->extend == TEX_REPEAT),
|
||||
(tex->extend == TEX_EXTEND));
|
||||
}
|
||||
else { /* no filtering */
|
||||
ibuf_get_color(texres->trgba, ibuf, x, y);
|
||||
}
|
||||
|
||||
if (texres->talpha) {
|
||||
texres->tin = texres->trgba[3];
|
||||
}
|
||||
else if (tex->imaflag & TEX_CALCALPHA) {
|
||||
texres->trgba[3] = texres->tin = std::max(
|
||||
{texres->trgba[0], texres->trgba[1], texres->trgba[2]});
|
||||
}
|
||||
else {
|
||||
texres->trgba[3] = texres->tin = 1.0;
|
||||
}
|
||||
|
||||
if (tex->flag & TEX_NEGALPHA) {
|
||||
texres->trgba[3] = 1.0f - texres->trgba[3];
|
||||
}
|
||||
|
||||
/* De-pre-multiply, this is being pre-multiplied in #shade_input_do_shade()
|
||||
* do not de-pre-multiply for generated alpha, it is already in straight. */
|
||||
if (texres->trgba[3] != 1.0f && texres->trgba[3] > 1e-4f && !(tex->imaflag & TEX_CALCALPHA)) {
|
||||
fx = 1.0f / texres->trgba[3];
|
||||
texres->trgba[0] *= fx;
|
||||
texres->trgba[1] *= fx;
|
||||
texres->trgba[2] *= fx;
|
||||
}
|
||||
|
||||
if (ima) {
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
}
|
||||
|
||||
BRICONTRGB;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void clipx_rctf_swap(rctf *stack, short *count, float x1, float x2)
|
||||
{
|
||||
rctf *rf, *newrct;
|
||||
short a;
|
||||
|
||||
a = *count;
|
||||
rf = stack;
|
||||
for (; a > 0; a--) {
|
||||
if (rf->xmin < x1) {
|
||||
if (rf->xmax < x1) {
|
||||
rf->xmin += (x2 - x1);
|
||||
rf->xmax += (x2 - x1);
|
||||
}
|
||||
else {
|
||||
rf->xmax = std::min(rf->xmax, x2);
|
||||
newrct = stack + *count;
|
||||
(*count)++;
|
||||
|
||||
newrct->xmax = x2;
|
||||
newrct->xmin = rf->xmin + (x2 - x1);
|
||||
newrct->ymin = rf->ymin;
|
||||
newrct->ymax = rf->ymax;
|
||||
|
||||
if (newrct->xmin == newrct->xmax) {
|
||||
(*count)--;
|
||||
}
|
||||
|
||||
rf->xmin = x1;
|
||||
}
|
||||
}
|
||||
else if (rf->xmax > x2) {
|
||||
if (rf->xmin > x2) {
|
||||
rf->xmin -= (x2 - x1);
|
||||
rf->xmax -= (x2 - x1);
|
||||
}
|
||||
else {
|
||||
rf->xmin = std::max(rf->xmin, x1);
|
||||
newrct = stack + *count;
|
||||
(*count)++;
|
||||
|
||||
newrct->xmin = x1;
|
||||
newrct->xmax = rf->xmax - (x2 - x1);
|
||||
newrct->ymin = rf->ymin;
|
||||
newrct->ymax = rf->ymax;
|
||||
|
||||
if (newrct->xmin == newrct->xmax) {
|
||||
(*count)--;
|
||||
}
|
||||
|
||||
rf->xmax = x2;
|
||||
}
|
||||
}
|
||||
rf++;
|
||||
}
|
||||
}
|
||||
|
||||
static void clipy_rctf_swap(rctf *stack, short *count, float y1, float y2)
|
||||
{
|
||||
rctf *rf, *newrct;
|
||||
short a;
|
||||
|
||||
a = *count;
|
||||
rf = stack;
|
||||
for (; a > 0; a--) {
|
||||
if (rf->ymin < y1) {
|
||||
if (rf->ymax < y1) {
|
||||
rf->ymin += (y2 - y1);
|
||||
rf->ymax += (y2 - y1);
|
||||
}
|
||||
else {
|
||||
rf->ymax = std::min(rf->ymax, y2);
|
||||
newrct = stack + *count;
|
||||
(*count)++;
|
||||
|
||||
newrct->ymax = y2;
|
||||
newrct->ymin = rf->ymin + (y2 - y1);
|
||||
newrct->xmin = rf->xmin;
|
||||
newrct->xmax = rf->xmax;
|
||||
|
||||
if (newrct->ymin == newrct->ymax) {
|
||||
(*count)--;
|
||||
}
|
||||
|
||||
rf->ymin = y1;
|
||||
}
|
||||
}
|
||||
else if (rf->ymax > y2) {
|
||||
if (rf->ymin > y2) {
|
||||
rf->ymin -= (y2 - y1);
|
||||
rf->ymax -= (y2 - y1);
|
||||
}
|
||||
else {
|
||||
rf->ymin = std::max(rf->ymin, y1);
|
||||
newrct = stack + *count;
|
||||
(*count)++;
|
||||
|
||||
newrct->ymin = y1;
|
||||
newrct->ymax = rf->ymax - (y2 - y1);
|
||||
newrct->xmin = rf->xmin;
|
||||
newrct->xmax = rf->xmax;
|
||||
|
||||
if (newrct->ymin == newrct->ymax) {
|
||||
(*count)--;
|
||||
}
|
||||
|
||||
rf->ymax = y2;
|
||||
}
|
||||
}
|
||||
rf++;
|
||||
}
|
||||
}
|
||||
|
||||
static float square_rctf(const rctf *rf)
|
||||
{
|
||||
float x, y;
|
||||
|
||||
x = BLI_rctf_size_x(rf);
|
||||
y = BLI_rctf_size_y(rf);
|
||||
return x * y;
|
||||
}
|
||||
|
||||
static float clipx_rctf(rctf *rf, float x1, float x2)
|
||||
{
|
||||
float size;
|
||||
|
||||
size = BLI_rctf_size_x(rf);
|
||||
|
||||
rf->xmin = std::max(rf->xmin, x1);
|
||||
rf->xmax = std::min(rf->xmax, x2);
|
||||
if (rf->xmin > rf->xmax) {
|
||||
rf->xmin = rf->xmax;
|
||||
return 0.0;
|
||||
}
|
||||
if (size != 0.0f) {
|
||||
return BLI_rctf_size_x(rf) / size;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static float clipy_rctf(rctf *rf, float y1, float y2)
|
||||
{
|
||||
float size;
|
||||
|
||||
size = BLI_rctf_size_y(rf);
|
||||
|
||||
rf->ymin = std::max(rf->ymin, y1);
|
||||
rf->ymax = std::min(rf->ymax, y2);
|
||||
|
||||
if (rf->ymin > rf->ymax) {
|
||||
rf->ymin = rf->ymax;
|
||||
return 0.0;
|
||||
}
|
||||
if (size != 0.0f) {
|
||||
return BLI_rctf_size_y(rf) / size;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
static void boxsampleclip(ImBuf *ibuf, const rctf *rf, TexResult *texres)
|
||||
{
|
||||
/* Sample box, is clipped already, and minx etc. have been set at ibuf size.
|
||||
* Enlarge with anti-aliased edges of the pixels. */
|
||||
|
||||
float muly, mulx, div, col[4];
|
||||
int x, y, startx, endx, starty, endy;
|
||||
|
||||
startx = int(floor(rf->xmin));
|
||||
endx = int(floor(rf->xmax));
|
||||
starty = int(floor(rf->ymin));
|
||||
endy = int(floor(rf->ymax));
|
||||
|
||||
startx = std::max(startx, 0);
|
||||
starty = std::max(starty, 0);
|
||||
if (endx >= ibuf->x) {
|
||||
endx = ibuf->x - 1;
|
||||
}
|
||||
if (endy >= ibuf->y) {
|
||||
endy = ibuf->y - 1;
|
||||
}
|
||||
|
||||
if (starty == endy && startx == endx) {
|
||||
ibuf_get_color(texres->trgba, ibuf, startx, starty);
|
||||
}
|
||||
else {
|
||||
div = texres->trgba[0] = texres->trgba[1] = texres->trgba[2] = texres->trgba[3] = 0.0;
|
||||
for (y = starty; y <= endy; y++) {
|
||||
|
||||
muly = 1.0;
|
||||
|
||||
if (starty == endy) {
|
||||
/* pass */
|
||||
}
|
||||
else {
|
||||
if (y == starty) {
|
||||
muly = 1.0f - (rf->ymin - y);
|
||||
}
|
||||
if (y == endy) {
|
||||
muly = (rf->ymax - y);
|
||||
}
|
||||
}
|
||||
|
||||
if (startx == endx) {
|
||||
mulx = muly;
|
||||
|
||||
ibuf_get_color(col, ibuf, startx, y);
|
||||
madd_v4_v4fl(texres->trgba, col, mulx);
|
||||
div += mulx;
|
||||
}
|
||||
else {
|
||||
for (x = startx; x <= endx; x++) {
|
||||
mulx = muly;
|
||||
if (x == startx) {
|
||||
mulx *= 1.0f - (rf->xmin - x);
|
||||
}
|
||||
if (x == endx) {
|
||||
mulx *= (rf->xmax - x);
|
||||
}
|
||||
|
||||
ibuf_get_color(col, ibuf, x, y);
|
||||
/* TODO(jbakker): No need to do manual optimization. Branching is slower than multiplying
|
||||
* with 1. */
|
||||
if (mulx == 1.0f) {
|
||||
add_v4_v4(texres->trgba, col);
|
||||
div += 1.0f;
|
||||
}
|
||||
else {
|
||||
madd_v4_v4fl(texres->trgba, col, mulx);
|
||||
div += mulx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (div != 0.0f) {
|
||||
div = 1.0f / div;
|
||||
mul_v4_fl(texres->trgba, div);
|
||||
}
|
||||
else {
|
||||
zero_v4(texres->trgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void boxsample(ImBuf *ibuf,
|
||||
float minx,
|
||||
float miny,
|
||||
float maxx,
|
||||
float maxy,
|
||||
TexResult *texres,
|
||||
const short imaprepeat,
|
||||
const short imapextend)
|
||||
{
|
||||
/* Sample box, performs clip. minx etc are in range 0.0 - 1.0 .
|
||||
* Enlarge with anti-aliased edges of pixels.
|
||||
* If variable 'imaprepeat' has been set, the
|
||||
* clipped-away parts are sampled as well.
|
||||
*/
|
||||
/* NOTE: actually minx etc isn't in the proper range...
|
||||
* this due to filter size and offset vectors for bump. */
|
||||
/* NOTE: talpha must be initialized. */
|
||||
/* NOTE: even when 'imaprepeat' is set, this can only repeat once in any direction.
|
||||
* the point which min/max is derived from is assumed to be wrapped. */
|
||||
TexResult texr;
|
||||
rctf *rf, stack[8];
|
||||
float opp, tot, alphaclip = 1.0;
|
||||
short count = 1;
|
||||
|
||||
rf = stack;
|
||||
rf->xmin = minx * (ibuf->x);
|
||||
rf->xmax = maxx * (ibuf->x);
|
||||
rf->ymin = miny * (ibuf->y);
|
||||
rf->ymax = maxy * (ibuf->y);
|
||||
|
||||
texr.talpha = texres->talpha; /* is read by boxsample_clip */
|
||||
|
||||
if (imapextend) {
|
||||
CLAMP(rf->xmin, 0.0f, ibuf->x - 1);
|
||||
CLAMP(rf->xmax, 0.0f, ibuf->x - 1);
|
||||
}
|
||||
else if (imaprepeat) {
|
||||
clipx_rctf_swap(stack, &count, 0.0, float(ibuf->x));
|
||||
}
|
||||
else {
|
||||
alphaclip = clipx_rctf(rf, 0.0, float(ibuf->x));
|
||||
|
||||
if (alphaclip <= 0.0f) {
|
||||
texres->trgba[0] = texres->trgba[2] = texres->trgba[1] = texres->trgba[3] = 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (imapextend) {
|
||||
CLAMP(rf->ymin, 0.0f, ibuf->y - 1);
|
||||
CLAMP(rf->ymax, 0.0f, ibuf->y - 1);
|
||||
}
|
||||
else if (imaprepeat) {
|
||||
clipy_rctf_swap(stack, &count, 0.0, float(ibuf->y));
|
||||
}
|
||||
else {
|
||||
alphaclip *= clipy_rctf(rf, 0.0, float(ibuf->y));
|
||||
|
||||
if (alphaclip <= 0.0f) {
|
||||
texres->trgba[0] = texres->trgba[2] = texres->trgba[1] = texres->trgba[3] = 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
tot = texres->trgba[0] = texres->trgba[2] = texres->trgba[1] = texres->trgba[3] = 0.0;
|
||||
while (count--) {
|
||||
boxsampleclip(ibuf, rf, &texr);
|
||||
|
||||
opp = square_rctf(rf);
|
||||
tot += opp;
|
||||
|
||||
texres->trgba[0] += opp * texr.trgba[0];
|
||||
texres->trgba[1] += opp * texr.trgba[1];
|
||||
texres->trgba[2] += opp * texr.trgba[2];
|
||||
if (texres->talpha) {
|
||||
texres->trgba[3] += opp * texr.trgba[3];
|
||||
}
|
||||
rf++;
|
||||
}
|
||||
if (tot != 0.0f) {
|
||||
texres->trgba[0] /= tot;
|
||||
texres->trgba[1] /= tot;
|
||||
texres->trgba[2] /= tot;
|
||||
if (texres->talpha) {
|
||||
texres->trgba[3] /= tot;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
boxsampleclip(ibuf, rf, texres);
|
||||
}
|
||||
|
||||
if (texres->talpha == 0) {
|
||||
texres->trgba[3] = 1.0;
|
||||
}
|
||||
|
||||
if (alphaclip != 1.0f) {
|
||||
/* Pre-multiply it all. */
|
||||
texres->trgba[0] *= alphaclip;
|
||||
texres->trgba[1] *= alphaclip;
|
||||
texres->trgba[2] *= alphaclip;
|
||||
texres->trgba[3] *= alphaclip;
|
||||
}
|
||||
}
|
||||
|
||||
void image_sample(
|
||||
Image *ima, float fx, float fy, float dx, float dy, float result[4], ImagePool *pool)
|
||||
{
|
||||
TexResult texres;
|
||||
ImBuf *ibuf = BKE_image_pool_acquire_ibuf(ima, nullptr, pool);
|
||||
|
||||
if (UNLIKELY(ibuf == nullptr)) {
|
||||
zero_v4(result);
|
||||
return;
|
||||
}
|
||||
|
||||
texres.talpha = true; /* boxsample expects to be initialized */
|
||||
boxsample(ibuf, fx, fy, fx + dx, fy + dy, &texres, 0, 1);
|
||||
copy_v4_v4(result, texres.trgba);
|
||||
|
||||
ima->flag |= IMA_USED_FOR_RENDER;
|
||||
|
||||
BKE_image_pool_release_ibuf(ima, ibuf, pool);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
595
blender-5.2.0/source/blender/render/intern/texture_margin.cc
Normal file
595
blender-5.2.0/source/blender/render/intern/texture_margin.cc
Normal file
@@ -0,0 +1,595 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_mesh_mapping.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_interp.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "zbuf.h" /* For rasterizer (#ZSpan and associated functions). */
|
||||
|
||||
#include "RE_texture_margin.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace render::texturemargin {
|
||||
|
||||
/**
|
||||
* The map class contains both a pixel map which maps out face indices for all UV-polygons and
|
||||
* adjacency tables.
|
||||
*/
|
||||
class TextureMarginMap {
|
||||
static const int directions[8][2];
|
||||
static const int distances[8];
|
||||
|
||||
/** Maps UV-edges to their corresponding UV-edge. */
|
||||
Vector<int> loop_adjacency_map_;
|
||||
/** Maps UV-edges to their corresponding face. */
|
||||
Array<int> loop_to_face_map_;
|
||||
|
||||
int w_, h_;
|
||||
float uv_offset_[2];
|
||||
Vector<uint32_t> pixel_data_;
|
||||
ZSpan zspan_;
|
||||
uint32_t value_to_store_;
|
||||
bool write_mask_;
|
||||
char *mask_;
|
||||
|
||||
OffsetIndices<int> faces_;
|
||||
Span<int> corner_edges_;
|
||||
Span<float2> uv_map_;
|
||||
int totedge_;
|
||||
|
||||
public:
|
||||
TextureMarginMap(size_t w,
|
||||
size_t h,
|
||||
const float uv_offset[2],
|
||||
const int totedge,
|
||||
const OffsetIndices<int> faces,
|
||||
const Span<int> corner_edges,
|
||||
const Span<float2> uv_map)
|
||||
: w_(w),
|
||||
h_(h),
|
||||
faces_(faces),
|
||||
corner_edges_(corner_edges),
|
||||
uv_map_(uv_map),
|
||||
totedge_(totedge)
|
||||
{
|
||||
copy_v2_v2(uv_offset_, uv_offset);
|
||||
|
||||
pixel_data_.resize(w_ * h_, 0xFFFFFFFF);
|
||||
|
||||
zbuf_alloc_span(&zspan_, w_, h_);
|
||||
|
||||
build_tables();
|
||||
}
|
||||
|
||||
~TextureMarginMap()
|
||||
{
|
||||
zbuf_free_span(&zspan_);
|
||||
}
|
||||
|
||||
void set_pixel(int x, int y, uint32_t value)
|
||||
{
|
||||
BLI_assert(x < w_);
|
||||
BLI_assert(x >= 0);
|
||||
pixel_data_[y * w_ + x] = value;
|
||||
}
|
||||
|
||||
uint32_t get_pixel(int x, int y) const
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= w_ || y >= h_) {
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
return pixel_data_[y * w_ + x];
|
||||
}
|
||||
|
||||
void rasterize_tri(float *v1, float *v2, float *v3, uint32_t value, char *mask, bool writemask)
|
||||
{
|
||||
/* NOTE: This is not thread safe, because the value to be written by the rasterizer is
|
||||
* a class member. If this is ever made multi-threaded each thread needs to get its own. */
|
||||
value_to_store_ = value;
|
||||
mask_ = mask;
|
||||
write_mask_ = writemask;
|
||||
zspan_scanconvert(
|
||||
&zspan_, this, &(v1[0]), &(v2[0]), &(v3[0]), TextureMarginMap::zscan_store_pixel);
|
||||
}
|
||||
|
||||
static void zscan_store_pixel(
|
||||
void *map, int x, int y, [[maybe_unused]] float u, [[maybe_unused]] float v)
|
||||
{
|
||||
/* NOTE: Not thread safe, see comment above. */
|
||||
TextureMarginMap *m = static_cast<TextureMarginMap *>(map);
|
||||
if (m->mask_) {
|
||||
if (m->write_mask_) {
|
||||
/* if there is a mask and write_mask_ is true, write to the mask */
|
||||
m->mask_[y * m->w_ + x] = 1;
|
||||
m->set_pixel(x, y, m->value_to_store_);
|
||||
}
|
||||
else {
|
||||
/* if there is a mask and write_mask_ is false, read the mask
|
||||
* to decide if the map needs to be written
|
||||
*/
|
||||
if (m->mask_[y * m->w_ + x] != 0) {
|
||||
m->set_pixel(x, y, m->value_to_store_);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m->set_pixel(x, y, m->value_to_store_);
|
||||
}
|
||||
}
|
||||
|
||||
/* The map contains 2 kinds of pixels: DijkstraPixels and face indices. The top bit determines
|
||||
* what kind it is. With the top bit set, it is a 'dijkstra' pixel. The bottom 4 bits encode the
|
||||
* direction of the shortest path and the remaining 27 bits are used to store the distance. If
|
||||
* the top bit is not set, the rest of the bits is used to store the face index.
|
||||
*/
|
||||
#define PackDijkstraPixel(dist, dir) (0x80000000 + ((dist) << 4) + (dir))
|
||||
#define DijkstraPixelGetDistance(dp) (((dp) ^ 0x80000000) >> 4)
|
||||
#define DijkstraPixelGetDirection(dp) ((dp) & 0xF)
|
||||
#define IsDijkstraPixel(dp) ((dp) & 0x80000000)
|
||||
#define DijkstraPixelIsUnset(dp) ((dp) == 0xFFFFFFFF)
|
||||
|
||||
/**
|
||||
* Use dijkstra's algorithm to 'grow' a border around the polygons marked in the map.
|
||||
* For each pixel mark which direction is the shortest way to a face.
|
||||
*/
|
||||
void grow_dijkstra(int margin)
|
||||
{
|
||||
class DijkstraActivePixel {
|
||||
public:
|
||||
DijkstraActivePixel(int dist, int _x, int _y) : distance(dist), x(_x), y(_y) {}
|
||||
int distance;
|
||||
int x, y;
|
||||
};
|
||||
auto cmp_dijkstrapixel_fun = [](DijkstraActivePixel const &a1, DijkstraActivePixel const &a2) {
|
||||
return a1.distance > a2.distance;
|
||||
};
|
||||
|
||||
Vector<DijkstraActivePixel> active_pixels;
|
||||
for (int y = 0; y < h_; y++) {
|
||||
for (int x = 0; x < w_; x++) {
|
||||
if (DijkstraPixelIsUnset(get_pixel(x, y))) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int xx = x - directions[i][0];
|
||||
int yy = y - directions[i][1];
|
||||
|
||||
if (xx >= 0 && xx < w_ && yy >= 0 && yy < w_ && !IsDijkstraPixel(get_pixel(xx, yy))) {
|
||||
set_pixel(x, y, PackDijkstraPixel(distances[i], i));
|
||||
active_pixels.append(DijkstraActivePixel(distances[i], x, y));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Not strictly needed because at this point it already is a heap. */
|
||||
#if 0
|
||||
std::make_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun);
|
||||
#endif
|
||||
|
||||
while (active_pixels.size()) {
|
||||
std::pop_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun);
|
||||
DijkstraActivePixel p = active_pixels.pop_last();
|
||||
|
||||
int dist = p.distance;
|
||||
|
||||
if (dist < 2 * (margin + 1)) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int x = p.x + directions[i][0];
|
||||
int y = p.y + directions[i][1];
|
||||
if (x >= 0 && x < w_ && y >= 0 && y < h_) {
|
||||
uint32_t dp = get_pixel(x, y);
|
||||
if (IsDijkstraPixel(dp) && (DijkstraPixelGetDistance(dp) > dist + distances[i])) {
|
||||
BLI_assert(DijkstraPixelGetDirection(dp) != i);
|
||||
set_pixel(x, y, PackDijkstraPixel(dist + distances[i], i));
|
||||
active_pixels.append(DijkstraActivePixel(dist + distances[i], x, y));
|
||||
std::push_heap(active_pixels.begin(), active_pixels.end(), cmp_dijkstrapixel_fun);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk over the map and for margin pixels follow the direction stored in the bottom 3
|
||||
* bits back to the face.
|
||||
* Then look up the pixel from the next face.
|
||||
*/
|
||||
void lookup_pixels(ImBuf *ibuf, char *mask, int maxPolygonSteps)
|
||||
{
|
||||
float4 *ibuf_ptr_fl = reinterpret_cast<float4 *>(ibuf->float_data_for_write());
|
||||
uchar4 *ibuf_ptr_ch = reinterpret_cast<uchar4 *>(ibuf->byte_data_for_write());
|
||||
size_t pixel_index = 0;
|
||||
for (int y = 0; y < h_; y++) {
|
||||
for (int x = 0; x < w_; x++) {
|
||||
uint32_t dp = pixel_data_[pixel_index];
|
||||
if (IsDijkstraPixel(dp) && !DijkstraPixelIsUnset(dp)) {
|
||||
int dist = DijkstraPixelGetDistance(dp);
|
||||
int direction = DijkstraPixelGetDirection(dp);
|
||||
|
||||
int xx = x;
|
||||
int yy = y;
|
||||
|
||||
/* Follow the dijkstra directions to find the face this margin pixels belongs to. */
|
||||
while (dist > 0) {
|
||||
xx -= directions[direction][0];
|
||||
yy -= directions[direction][1];
|
||||
dp = get_pixel(xx, yy);
|
||||
dist -= distances[direction];
|
||||
BLI_assert(!dist || (dist == DijkstraPixelGetDistance(dp)));
|
||||
direction = DijkstraPixelGetDirection(dp);
|
||||
}
|
||||
|
||||
uint32_t face = get_pixel(xx, yy);
|
||||
|
||||
BLI_assert(!IsDijkstraPixel(face));
|
||||
|
||||
float destX, destY;
|
||||
|
||||
int other_poly;
|
||||
bool found_pixel_in_polygon = false;
|
||||
if (lookup_pixel_polygon_neighborhood(x, y, &face, &destX, &destY, &other_poly)) {
|
||||
|
||||
for (int i = 0; i < maxPolygonSteps; i++) {
|
||||
/* Force to pixel grid. */
|
||||
int nx = int(round(destX));
|
||||
int ny = int(round(destY));
|
||||
uint32_t polygon_from_map = get_pixel(nx, ny);
|
||||
if (other_poly == polygon_from_map) {
|
||||
found_pixel_in_polygon = true;
|
||||
break;
|
||||
}
|
||||
|
||||
float dist_to_edge;
|
||||
/* Look up again, but starting from the face we were expected to land in. */
|
||||
if (!lookup_pixel(nx, ny, other_poly, &destX, &destY, &other_poly, &dist_to_edge)) {
|
||||
found_pixel_in_polygon = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_pixel_in_polygon) {
|
||||
if (ibuf_ptr_fl) {
|
||||
ibuf_ptr_fl[pixel_index] = imbuf::interpolate_bilinear_border_fl(
|
||||
ibuf, destX, destY);
|
||||
}
|
||||
if (ibuf_ptr_ch) {
|
||||
ibuf_ptr_ch[pixel_index] = imbuf::interpolate_bilinear_border_byte(
|
||||
ibuf, destX, destY);
|
||||
}
|
||||
/* Add our new pixels to the assigned pixel map. */
|
||||
mask[pixel_index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (DijkstraPixelIsUnset(dp) || !IsDijkstraPixel(dp)) {
|
||||
/* These are not margin pixels, make sure the extend filter which is run after this step
|
||||
* leaves them alone.
|
||||
*/
|
||||
mask[pixel_index] = 1;
|
||||
}
|
||||
pixel_index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float2 uv_to_xy(const float2 &uv_map) const
|
||||
{
|
||||
float2 ret;
|
||||
ret.x = (((uv_map[0] - uv_offset_[0]) * w_) - (0.5f + 0.001f));
|
||||
ret.y = (((uv_map[1] - uv_offset_[1]) * h_) - (0.5f + 0.001f));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void build_tables()
|
||||
{
|
||||
loop_to_face_map_ = bke::mesh::build_corner_to_face_map(faces_);
|
||||
|
||||
loop_adjacency_map_.resize(corner_edges_.size(), -1);
|
||||
|
||||
Vector<int> tmpmap;
|
||||
tmpmap.resize(totedge_, -1);
|
||||
|
||||
for (const int64_t i : corner_edges_.index_range()) {
|
||||
int edge = corner_edges_[i];
|
||||
if (tmpmap[edge] == -1) {
|
||||
loop_adjacency_map_[i] = -1;
|
||||
tmpmap[edge] = i;
|
||||
}
|
||||
else {
|
||||
BLI_assert(tmpmap[edge] >= 0);
|
||||
loop_adjacency_map_[i] = tmpmap[edge];
|
||||
loop_adjacency_map_[tmpmap[edge]] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call lookup_pixel for the start_poly. If that fails, try the adjacent polygons as well.
|
||||
* Because the Dijkstra is not very exact in determining which face is the closest, the
|
||||
* face we need can be the one next to the one the Dijkstra map provides. To prevent missing
|
||||
* pixels also check the neighboring polygons.
|
||||
*/
|
||||
bool lookup_pixel_polygon_neighborhood(
|
||||
float x, float y, uint32_t *r_start_poly, float *r_destx, float *r_desty, int *r_other_poly)
|
||||
{
|
||||
float found_dist;
|
||||
if (lookup_pixel(x, y, *r_start_poly, r_destx, r_desty, r_other_poly, &found_dist)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int loopstart = faces_[*r_start_poly].start();
|
||||
int totloop = faces_[*r_start_poly].size();
|
||||
|
||||
float destx, desty;
|
||||
int foundpoly;
|
||||
|
||||
float mindist = -1.0f;
|
||||
|
||||
/* Loop over all adjacent polygons and determine which edge is closest.
|
||||
* This could be optimized by only inspecting neighbors which are on the edge of an island.
|
||||
* But it seems fast enough for now and that would add a lot of complexity. */
|
||||
for (int i = 0; i < totloop; i++) {
|
||||
int otherloop = loop_adjacency_map_[i + loopstart];
|
||||
|
||||
if (otherloop < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t face = loop_to_face_map_[otherloop];
|
||||
|
||||
if (lookup_pixel(x, y, face, &destx, &desty, &foundpoly, &found_dist)) {
|
||||
if (mindist < 0.0f || found_dist < mindist) {
|
||||
mindist = found_dist;
|
||||
*r_other_poly = foundpoly;
|
||||
*r_destx = destx;
|
||||
*r_desty = desty;
|
||||
*r_start_poly = face;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mindist >= 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find which edge of the src_poly is closest to x,y. Look up its adjacent UV-edge and face.
|
||||
* Then return the location of the equivalent pixel in the other face.
|
||||
* Returns true if a new pixel location was found, false if it wasn't, which can happen if the
|
||||
* margin pixel is on a corner, or the UV-edge doesn't have an adjacent face.
|
||||
*/
|
||||
bool lookup_pixel(float x,
|
||||
float y,
|
||||
int src_poly,
|
||||
float *r_destx,
|
||||
float *r_desty,
|
||||
int *r_other_poly,
|
||||
float *r_dist_to_edge)
|
||||
{
|
||||
float2 point(x, y);
|
||||
|
||||
*r_destx = *r_desty = 0;
|
||||
|
||||
int found_edge = -1;
|
||||
float found_dist = -1;
|
||||
float found_t = 0;
|
||||
|
||||
/* Find the closest edge on which the point x,y can be projected.
|
||||
*/
|
||||
for (size_t i = 0; i < faces_[src_poly].size(); i++) {
|
||||
int l1 = faces_[src_poly].start() + i;
|
||||
int l2 = l1 + 1;
|
||||
if (l2 >= faces_[src_poly].start() + faces_[src_poly].size()) {
|
||||
l2 = faces_[src_poly].start();
|
||||
}
|
||||
/* edge points */
|
||||
float2 edgepoint1 = uv_to_xy(uv_map_[l1]);
|
||||
float2 edgepoint2 = uv_to_xy(uv_map_[l2]);
|
||||
/* Vector AB is the vector from the first edge point to the second edge point.
|
||||
* Vector AP is the vector from the first edge point to our point under investigation. */
|
||||
float2 ab = edgepoint2 - edgepoint1;
|
||||
float2 ap = point - edgepoint1;
|
||||
|
||||
/* Project ap onto ab. */
|
||||
float dotv = math::dot(ab, ap);
|
||||
|
||||
float ablensq = math::length_squared(ab);
|
||||
|
||||
float t = dotv / ablensq;
|
||||
|
||||
if (t >= 0.0 && t <= 1.0) {
|
||||
|
||||
/* Find the point on the edge closest to P */
|
||||
float2 reflect_point = edgepoint1 + (t * ab);
|
||||
/* This is the vector to P, so 90 degrees out from the edge. */
|
||||
float2 reflect_vec = reflect_point - point;
|
||||
|
||||
float reflectLen = sqrt(reflect_vec[0] * reflect_vec[0] + reflect_vec[1] * reflect_vec[1]);
|
||||
float cross = ab[0] * reflect_vec[1] - ab[1] * reflect_vec[0];
|
||||
/* Only if P is on the outside of the edge, which means the cross product is positive,
|
||||
* we consider this edge.
|
||||
*/
|
||||
bool valid = (cross > 0.0);
|
||||
|
||||
if (valid && (found_dist < 0 || reflectLen < found_dist)) {
|
||||
/* Stother_ab the info of the closest edge so far. */
|
||||
found_dist = reflectLen;
|
||||
found_t = t;
|
||||
found_edge = i + faces_[src_poly].start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found_edge < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*r_dist_to_edge = found_dist;
|
||||
|
||||
/* Get the 'other' edge. I.E. the UV edge from the neighbor face. */
|
||||
int other_edge = loop_adjacency_map_[found_edge];
|
||||
|
||||
if (other_edge < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int dst_poly = loop_to_face_map_[other_edge];
|
||||
|
||||
if (r_other_poly) {
|
||||
*r_other_poly = dst_poly;
|
||||
}
|
||||
|
||||
int other_edge2 = other_edge + 1;
|
||||
if (other_edge2 >= faces_[dst_poly].start() + faces_[dst_poly].size()) {
|
||||
other_edge2 = faces_[dst_poly].start();
|
||||
}
|
||||
|
||||
float2 other_edgepoint1 = uv_to_xy(uv_map_[other_edge]);
|
||||
float2 other_edgepoint2 = uv_to_xy(uv_map_[other_edge2]);
|
||||
|
||||
/* Calculate the vector from the order edges last point to its first point. */
|
||||
float2 other_ab = other_edgepoint1 - other_edgepoint2;
|
||||
float2 other_reflect_point = other_edgepoint2 + (found_t * other_ab);
|
||||
float2 perpendicular_other_ab;
|
||||
perpendicular_other_ab.x = other_ab.y;
|
||||
perpendicular_other_ab.y = -other_ab.x;
|
||||
|
||||
/* The new point is dound_dist distance from other_reflect_point at a 90 degree angle to
|
||||
* other_ab */
|
||||
float2 new_point = other_reflect_point + (found_dist / math::length(perpendicular_other_ab)) *
|
||||
perpendicular_other_ab;
|
||||
|
||||
*r_destx = new_point.x;
|
||||
*r_desty = new_point.y;
|
||||
|
||||
return true;
|
||||
}
|
||||
}; // class TextureMarginMap
|
||||
|
||||
const int TextureMarginMap::directions[8][2] = {
|
||||
{-1, 0}, {-1, -1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}};
|
||||
const int TextureMarginMap::distances[8] = {2, 3, 2, 3, 2, 3, 2, 3};
|
||||
|
||||
static void generate_margin(ImBuf *ibuf,
|
||||
char *mask,
|
||||
const int margin,
|
||||
const Span<float3> vert_positions,
|
||||
const int edges_num,
|
||||
const OffsetIndices<int> faces,
|
||||
const Span<int> corner_edges,
|
||||
const Span<int> corner_verts,
|
||||
const Span<float2> uv_map,
|
||||
const float uv_offset[2])
|
||||
{
|
||||
Array<int3> corner_tris(poly_to_tri_count(faces.size(), corner_edges.size()));
|
||||
bke::mesh::corner_tris_calc(vert_positions, faces, corner_verts, corner_tris);
|
||||
|
||||
Array<int> tri_faces(corner_tris.size());
|
||||
bke::mesh::corner_tris_calc_face_indices(faces, tri_faces);
|
||||
|
||||
TextureMarginMap map(ibuf->x, ibuf->y, uv_offset, edges_num, faces, corner_edges, uv_map);
|
||||
|
||||
bool draw_new_mask = false;
|
||||
/* Now the map contains 3 sorts of values: 0xFFFFFFFF for empty pixels, `0x80000000 + polyindex`
|
||||
* for margin pixels, just `polyindex` for face pixels. */
|
||||
if (mask) {
|
||||
mask = MEM_dupalloc(mask);
|
||||
}
|
||||
else {
|
||||
mask = MEM_new_array_zeroed<char>(size_t(ibuf->x) * size_t(ibuf->y), __func__);
|
||||
draw_new_mask = true;
|
||||
}
|
||||
|
||||
for (const int i : corner_tris.index_range()) {
|
||||
const int3 tri = corner_tris[i];
|
||||
float vec[3][2];
|
||||
|
||||
for (int a = 0; a < 3; a++) {
|
||||
const float *uv = uv_map[tri[a]];
|
||||
|
||||
/* NOTE(@ideasman42): workaround for pixel aligned UVs which are common and can screw up
|
||||
* our intersection tests where a pixel gets in between 2 faces or the middle of a quad,
|
||||
* camera aligned quads also have this problem but they are less common.
|
||||
* Add a small offset to the UVs, fixes bug #18685. */
|
||||
vec[a][0] = (uv[0] - uv_offset[0]) * float(ibuf->x) - (0.5f + 0.001f);
|
||||
vec[a][1] = (uv[1] - uv_offset[1]) * float(ibuf->y) - (0.5f + 0.002f);
|
||||
}
|
||||
|
||||
/* NOTE: we need the top bit for the dijkstra distance map. */
|
||||
BLI_assert(tri_faces[i] < 0x80000000);
|
||||
|
||||
map.rasterize_tri(vec[0], vec[1], vec[2], tri_faces[i], mask, draw_new_mask);
|
||||
}
|
||||
|
||||
char *tmpmask = MEM_dupalloc(mask);
|
||||
/* Extend (with averaging) by 2 pixels. Those will be overwritten, but it
|
||||
* helps linear interpolations on the edges of polygons. */
|
||||
IMB_filter_extend(ibuf, tmpmask, 2);
|
||||
MEM_delete(tmpmask);
|
||||
|
||||
map.grow_dijkstra(margin);
|
||||
|
||||
/* Looking further than 3 polygons away leads to so much cumulative rounding
|
||||
* that it isn't worth it. So hard-code it to 3. */
|
||||
map.lookup_pixels(ibuf, mask, 3);
|
||||
|
||||
/* Use the extend filter to fill in the missing pixels at the corners, not strictly correct, but
|
||||
* the visual difference seems very minimal. This also catches pixels we missed because of very
|
||||
* narrow polygons.
|
||||
*/
|
||||
IMB_filter_extend(ibuf, mask, margin);
|
||||
|
||||
MEM_delete(mask);
|
||||
}
|
||||
|
||||
} // namespace render::texturemargin
|
||||
|
||||
void RE_generate_texturemargin_adjacentfaces(ImBuf *ibuf,
|
||||
char *mask,
|
||||
const int margin,
|
||||
const Mesh *mesh,
|
||||
StringRef uv_layer,
|
||||
const float uv_offset[2])
|
||||
{
|
||||
const StringRef name = uv_layer.is_empty() ? mesh->active_uv_map_name() : uv_layer;
|
||||
const bke::AttributeAccessor attributes = mesh->attributes();
|
||||
const VArraySpan<float2> uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Corner);
|
||||
|
||||
render::texturemargin::generate_margin(ibuf,
|
||||
mask,
|
||||
margin,
|
||||
mesh->vert_positions(),
|
||||
mesh->edges_num,
|
||||
mesh->faces(),
|
||||
mesh->corner_edges(),
|
||||
mesh->corner_verts(),
|
||||
uv_map,
|
||||
uv_offset);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
1113
blender-5.2.0/source/blender/render/intern/texture_procedural.cc
Normal file
1113
blender-5.2.0/source/blender/render/intern/texture_procedural.cc
Normal file
File diff suppressed because it is too large
Load Diff
100
blender-5.2.0/source/blender/render/intern/tile_highlight.cc
Normal file
100
blender-5.2.0/source/blender/render/intern/tile_highlight.cc
Normal file
@@ -0,0 +1,100 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#include "tile_highlight.h"
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_rect.h"
|
||||
|
||||
#include "RE_pipeline.h"
|
||||
|
||||
namespace blender::render {
|
||||
|
||||
TilesHighlight::Tile::Tile(const RenderResult *result) : rect(result->tilerect) {}
|
||||
|
||||
TilesHighlight::Tile::Tile(const int x, const int y, const int width, const int height)
|
||||
{
|
||||
BLI_rcti_init(&rect, x, x + width, y, y + height);
|
||||
}
|
||||
|
||||
uint64_t TilesHighlight::Tile::hash() const
|
||||
{
|
||||
return get_default_hash(rect.xmin, rect.xmax, rect.ymin, rect.ymax);
|
||||
}
|
||||
|
||||
void TilesHighlight::highlight_tile_for_result(const RenderResult *result)
|
||||
{
|
||||
const Tile tile(result);
|
||||
highlight_tile(tile);
|
||||
}
|
||||
|
||||
void TilesHighlight::unhighlight_tile_for_result(const RenderResult *result)
|
||||
{
|
||||
const Tile tile(result);
|
||||
unhighlight_tile(tile);
|
||||
}
|
||||
|
||||
void TilesHighlight::highlight_tile(const int x, const int y, const int width, const int height)
|
||||
{
|
||||
const Tile tile(x, y, width, height);
|
||||
highlight_tile(tile);
|
||||
}
|
||||
|
||||
void TilesHighlight::unhighlight_tile(const int x, const int y, const int width, const int height)
|
||||
{
|
||||
const Tile tile(x, y, width, height);
|
||||
unhighlight_tile(tile);
|
||||
}
|
||||
|
||||
void TilesHighlight::highlight_tile(const Tile &tile)
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
highlighted_tiles_set_.add(tile);
|
||||
did_tiles_change_ = true;
|
||||
}
|
||||
|
||||
void TilesHighlight::unhighlight_tile(const Tile &tile)
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
highlighted_tiles_set_.remove(tile);
|
||||
did_tiles_change_ = true;
|
||||
}
|
||||
|
||||
void TilesHighlight::clear()
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
highlighted_tiles_set_.clear();
|
||||
cached_highlighted_tiles_.clear_and_shrink();
|
||||
}
|
||||
|
||||
Span<rcti> TilesHighlight::get_all_highlighted_tiles() const
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
|
||||
/* Updated cached flat list if needed. */
|
||||
if (did_tiles_change_) {
|
||||
if (highlighted_tiles_set_.is_empty()) {
|
||||
cached_highlighted_tiles_.clear_and_shrink();
|
||||
}
|
||||
else {
|
||||
cached_highlighted_tiles_.reserve(highlighted_tiles_set_.size());
|
||||
for (const Tile &tile : highlighted_tiles_set_) {
|
||||
cached_highlighted_tiles_.append(tile.rect);
|
||||
}
|
||||
}
|
||||
|
||||
did_tiles_change_ = false;
|
||||
}
|
||||
|
||||
return cached_highlighted_tiles_;
|
||||
}
|
||||
|
||||
} // namespace blender::render
|
||||
70
blender-5.2.0/source/blender/render/intern/tile_highlight.h
Normal file
70
blender-5.2.0/source/blender/render/intern/tile_highlight.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_vec_types.h"
|
||||
|
||||
#include "BLI_mutex.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct RenderResult;
|
||||
|
||||
namespace render {
|
||||
|
||||
class TilesHighlight {
|
||||
public:
|
||||
TilesHighlight() = default;
|
||||
~TilesHighlight() = default;
|
||||
|
||||
void highlight_tile_for_result(const RenderResult *result);
|
||||
void unhighlight_tile_for_result(const RenderResult *result);
|
||||
|
||||
void highlight_tile(int x, int y, int width, int height);
|
||||
void unhighlight_tile(int x, int y, int width, int height);
|
||||
|
||||
void clear();
|
||||
|
||||
Span<rcti> get_all_highlighted_tiles() const;
|
||||
|
||||
private:
|
||||
struct Tile {
|
||||
Tile() = default;
|
||||
explicit Tile(const RenderResult *result);
|
||||
explicit Tile(int x, int y, int width, int height);
|
||||
|
||||
uint64_t hash() const;
|
||||
|
||||
bool operator==(const Tile &other) const
|
||||
{
|
||||
return rect == other.rect;
|
||||
}
|
||||
bool operator!=(const Tile &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
rcti rect = {0, 0, 0, 0};
|
||||
};
|
||||
|
||||
void highlight_tile(const Tile &tile);
|
||||
void unhighlight_tile(const Tile &tile);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
Set<Tile> highlighted_tiles_set_;
|
||||
|
||||
/* Cached flat list of currently highlighted tiles for a fast access via API. */
|
||||
mutable bool did_tiles_change_ = false;
|
||||
mutable Vector<rcti> cached_highlighted_tiles_;
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
} // namespace blender
|
||||
243
blender-5.2.0/source/blender/render/intern/zbuf.cc
Normal file
243
blender-5.2.0/source/blender/render/intern/zbuf.cc
Normal file
@@ -0,0 +1,243 @@
|
||||
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*
|
||||
* \note Some of this logic has been duplicated in `COM_VectorBlurOperation.cc`
|
||||
* changes here may also apply also apply to that file.
|
||||
*/
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Common includes */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
/* own includes */
|
||||
#include "zbuf.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* could enable at some point but for now there are far too many conversions */
|
||||
#ifdef __GNUC__
|
||||
# pragma GCC diagnostic ignored "-Wdouble-promotion"
|
||||
#endif
|
||||
|
||||
/* ****************** Spans ******************************* */
|
||||
|
||||
void zbuf_alloc_span(ZSpan *zspan, int rectx, int recty)
|
||||
{
|
||||
memset(zspan, 0, sizeof(ZSpan));
|
||||
|
||||
zspan->rectx = rectx;
|
||||
zspan->recty = recty;
|
||||
|
||||
zspan->span1 = MEM_new_array_uninitialized<float>(recty, "zspan");
|
||||
zspan->span2 = MEM_new_array_uninitialized<float>(recty, "zspan");
|
||||
}
|
||||
|
||||
void zbuf_free_span(ZSpan *zspan)
|
||||
{
|
||||
if (zspan) {
|
||||
MEM_SAFE_DELETE(zspan->span1);
|
||||
MEM_SAFE_DELETE(zspan->span2);
|
||||
}
|
||||
}
|
||||
|
||||
/* reset range for clipping */
|
||||
static void zbuf_init_span(ZSpan *zspan)
|
||||
{
|
||||
zspan->miny1 = zspan->miny2 = zspan->recty + 1;
|
||||
zspan->maxy1 = zspan->maxy2 = -1;
|
||||
zspan->minp1 = zspan->maxp1 = zspan->minp2 = zspan->maxp2 = nullptr;
|
||||
}
|
||||
|
||||
static void zbuf_add_to_span(ZSpan *zspan, const float v1[2], const float v2[2])
|
||||
{
|
||||
const float *minv, *maxv;
|
||||
float *span;
|
||||
float xx1, dx0, xs0;
|
||||
int y, my0, my2;
|
||||
|
||||
if (v1[1] < v2[1]) {
|
||||
minv = v1;
|
||||
maxv = v2;
|
||||
}
|
||||
else {
|
||||
minv = v2;
|
||||
maxv = v1;
|
||||
}
|
||||
|
||||
my0 = ceil(minv[1]);
|
||||
my2 = floor(maxv[1]);
|
||||
|
||||
if (my2 < 0 || my0 >= zspan->recty) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* clip top */
|
||||
if (my2 >= zspan->recty) {
|
||||
my2 = zspan->recty - 1;
|
||||
}
|
||||
/* clip bottom */
|
||||
my0 = std::max(my0, 0);
|
||||
|
||||
if (my0 > my2) {
|
||||
return;
|
||||
}
|
||||
/* if (my0>my2) should still fill in, that way we get spans that skip nicely */
|
||||
|
||||
xx1 = maxv[1] - minv[1];
|
||||
if (xx1 > FLT_EPSILON) {
|
||||
dx0 = (minv[0] - maxv[0]) / xx1;
|
||||
xs0 = dx0 * (minv[1] - my2) + minv[0];
|
||||
}
|
||||
else {
|
||||
dx0 = 0.0f;
|
||||
xs0 = min_ff(minv[0], maxv[0]);
|
||||
}
|
||||
|
||||
/* empty span */
|
||||
if (zspan->maxp1 == nullptr) {
|
||||
span = zspan->span1;
|
||||
}
|
||||
else { /* does it complete left span? */
|
||||
if (maxv == zspan->minp1 || minv == zspan->maxp1) {
|
||||
span = zspan->span1;
|
||||
}
|
||||
else {
|
||||
span = zspan->span2;
|
||||
}
|
||||
}
|
||||
|
||||
if (span == zspan->span1) {
|
||||
// printf("left span my0 %d my2 %d\n", my0, my2);
|
||||
if (zspan->minp1 == nullptr || zspan->minp1[1] > minv[1]) {
|
||||
zspan->minp1 = minv;
|
||||
}
|
||||
if (zspan->maxp1 == nullptr || zspan->maxp1[1] < maxv[1]) {
|
||||
zspan->maxp1 = maxv;
|
||||
}
|
||||
zspan->miny1 = std::min(my0, zspan->miny1);
|
||||
zspan->maxy1 = std::max(my2, zspan->maxy1);
|
||||
}
|
||||
else {
|
||||
// printf("right span my0 %d my2 %d\n", my0, my2);
|
||||
if (zspan->minp2 == nullptr || zspan->minp2[1] > minv[1]) {
|
||||
zspan->minp2 = minv;
|
||||
}
|
||||
if (zspan->maxp2 == nullptr || zspan->maxp2[1] < maxv[1]) {
|
||||
zspan->maxp2 = maxv;
|
||||
}
|
||||
zspan->miny2 = std::min(my0, zspan->miny2);
|
||||
zspan->maxy2 = std::max(my2, zspan->maxy2);
|
||||
}
|
||||
|
||||
for (y = my2; y >= my0; y--, xs0 += dx0) {
|
||||
/* xs0 is the X-coordinate! */
|
||||
span[y] = xs0;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
/* Functions */
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
void zspan_scanconvert(ZSpan *zspan,
|
||||
void *handle,
|
||||
float *v1,
|
||||
float *v2,
|
||||
float *v3,
|
||||
void (*func)(void *, int, int, float, float))
|
||||
{
|
||||
float x0, y0, x1, y1, x2, y2, z0, z1, z2;
|
||||
float u, v, uxd, uyd, vxd, vyd, uy0, vy0, xx1;
|
||||
const float *span1, *span2;
|
||||
int i, j, x, y, sn1, sn2, rectx = zspan->rectx, my0, my2;
|
||||
|
||||
/* init */
|
||||
zbuf_init_span(zspan);
|
||||
|
||||
/* set spans */
|
||||
zbuf_add_to_span(zspan, v1, v2);
|
||||
zbuf_add_to_span(zspan, v2, v3);
|
||||
zbuf_add_to_span(zspan, v3, v1);
|
||||
|
||||
/* clipped */
|
||||
if (zspan->minp2 == nullptr || zspan->maxp2 == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
my0 = max_ii(zspan->miny1, zspan->miny2);
|
||||
my2 = min_ii(zspan->maxy1, zspan->maxy2);
|
||||
|
||||
// printf("my %d %d\n", my0, my2);
|
||||
if (my2 < my0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* ZBUF DX DY, in floats still */
|
||||
x1 = v1[0] - v2[0];
|
||||
x2 = v2[0] - v3[0];
|
||||
y1 = v1[1] - v2[1];
|
||||
y2 = v2[1] - v3[1];
|
||||
|
||||
z1 = 1.0f; /* (u1 - u2) */
|
||||
z2 = 0.0f; /* (u2 - u3) */
|
||||
|
||||
x0 = y1 * z2 - z1 * y2;
|
||||
y0 = z1 * x2 - x1 * z2;
|
||||
z0 = x1 * y2 - y1 * x2;
|
||||
|
||||
if (z0 == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
xx1 = (x0 * v1[0] + y0 * v1[1]) / z0 + 1.0f;
|
||||
uxd = -double(x0) / double(z0);
|
||||
uyd = -double(y0) / double(z0);
|
||||
uy0 = double(my2) * uyd + double(xx1);
|
||||
|
||||
z1 = -1.0f; /* (v1 - v2) */
|
||||
z2 = 1.0f; /* (v2 - v3) */
|
||||
|
||||
x0 = y1 * z2 - z1 * y2;
|
||||
y0 = z1 * x2 - x1 * z2;
|
||||
|
||||
xx1 = (x0 * v1[0] + y0 * v1[1]) / z0;
|
||||
vxd = -double(x0) / double(z0);
|
||||
vyd = -double(y0) / double(z0);
|
||||
vy0 = double(my2) * vyd + double(xx1);
|
||||
|
||||
/* correct span */
|
||||
span1 = zspan->span1 + my2;
|
||||
span2 = zspan->span2 + my2;
|
||||
|
||||
for (i = 0, y = my2; y >= my0; i++, y--, span1--, span2--) {
|
||||
|
||||
sn1 = floor(min_ff(*span1, *span2));
|
||||
sn2 = floor(max_ff(*span1, *span2));
|
||||
sn1++;
|
||||
|
||||
if (sn2 >= rectx) {
|
||||
sn2 = rectx - 1;
|
||||
}
|
||||
sn1 = std::max(sn1, 0);
|
||||
|
||||
u = ((double(sn1) * uxd) + uy0) - (i * uyd);
|
||||
v = ((double(sn1) * vxd) + vy0) - (i * vyd);
|
||||
|
||||
for (j = 0, x = sn1; x <= sn2; j++, x++) {
|
||||
func(handle, x, y, u + (j * uxd), v + (j * vxd));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
39
blender-5.2.0/source/blender/render/intern/zbuf.h
Normal file
39
blender-5.2.0/source/blender/render/intern/zbuf.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup render
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace blender {
|
||||
|
||||
/** Span fill in method, is also used to localize data for Z-buffering. */
|
||||
struct ZSpan {
|
||||
int rectx, recty; /* range for clipping */
|
||||
|
||||
int miny1, maxy1, miny2, maxy2; /* actual filled in range */
|
||||
const float *minp1, *maxp1, *minp2, *maxp2; /* vertex pointers detect min/max range in */
|
||||
float *span1, *span2;
|
||||
};
|
||||
|
||||
/**
|
||||
* Each Z-buffer has coordinates transformed to local rect coordinates, so we can simply clip.
|
||||
*/
|
||||
void zbuf_alloc_span(struct ZSpan *zspan, int rectx, int recty);
|
||||
void zbuf_free_span(struct ZSpan *zspan);
|
||||
|
||||
/**
|
||||
* Scan-convert for strand triangles, calls function for each x, y coordinate
|
||||
* and gives UV barycentrics and z.
|
||||
*/
|
||||
void zspan_scanconvert(struct ZSpan *zspan,
|
||||
void *handle,
|
||||
float *v1,
|
||||
float *v2,
|
||||
float *v3,
|
||||
void (*func)(void *, int, int, float, float));
|
||||
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user