Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
PUBLIC .
intern
../compositor
../compositor/cached_resources
../compositor/derived_resources
../draw/intern
../gpu/intern
../makesrna
../simulation
../../../intern/mikktspace
../../../intern/mantaflow/extern
)
set(INC_SYS
)
set(SRC
intern/bake.cc
intern/compositor.cc
intern/engine.cc
intern/initrender.cc
intern/multires_bake.cc
intern/pipeline.cc
intern/render_result.cc
intern/render_types.cc
intern/texture_image.cc
intern/texture_margin.cc
intern/texture_procedural.cc
intern/tile_highlight.cc
intern/zbuf.cc
RE_bake.h
RE_compositor.hh
RE_engine.h
RE_multires_bake.h
RE_pipeline.h
RE_texture.h
RE_texture_margin.h
intern/pipeline.hh
intern/render_result.h
intern/render_types.h
intern/texture_common.h
intern/tile_highlight.h
intern/zbuf.h
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::draw
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::imbuf::movie
PRIVATE bf::intern::guardedalloc
bf_compositor
bf_imbuf_openexr
PRIVATE bf::intern::atomic
PRIVATE bf::intern::clog
PRIVATE bf::nodes
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
)
if(WITH_PYTHON)
add_definitions(-DWITH_PYTHON)
list(APPEND INC
../python
)
endif()
if(WITH_FREESTYLE)
list(APPEND INC
../freestyle
)
list(APPEND LIB
bf_freestyle
)
add_definitions(-DWITH_FREESTYLE)
endif()
if(WITH_HYDRA)
add_subdirectory(hydra)
endif()
blender_add_lib_nolist(bf_render "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::render ALIAS bf_render)

View File

@@ -0,0 +1,144 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*/
#pragma once
#include "BLI_string_ref.hh"
#include "DNA_scene_types.h"
#include "RE_pipeline.h"
namespace blender {
struct Depsgraph;
struct ImBuf;
struct Mesh;
struct Render;
struct BakeImage {
struct Image *image;
int tile_number;
float uv_offset[2];
int width;
int height;
size_t offset;
/* For associating render result layer with image. */
char render_layer_name[RE_MAXNAME];
};
struct BakeTargets {
/* All images of the object. */
BakeImage *images;
int images_num;
/* Lookup table from Material number to BakeImage. */
struct Image **material_to_image;
int materials_num;
/* Pixel buffer to bake to. */
float *result;
int pixels_num;
int channels_num;
/* Baking to non-color data image. */
bool is_noncolor;
};
struct BakePixel {
int primitive_id, object_id;
int seed;
float uv[2];
float du_dx, du_dy;
float dv_dx, dv_dy;
};
struct BakeHighPolyData {
struct Object *ob;
struct Object *ob_eval;
struct Mesh *mesh;
bool is_flip_object;
float obmat[4][4];
float imat[4][4];
};
/* `external_engine.cc` */
bool RE_bake_has_engine(const struct Render *re);
bool RE_bake_engine(struct Render *re,
struct Depsgraph *depsgraph,
struct Object *object,
int object_id,
const BakePixel pixel_array[],
const BakeTargets *targets,
eScenePassType pass_type,
int pass_filter,
float result[]);
/* `bake.cc` */
int RE_pass_depth(eScenePassType pass_type);
bool RE_bake_pixels_populate_from_objects(struct Mesh *me_low,
BakePixel pixel_array_from[],
BakePixel pixel_array_to[],
BakeHighPolyData highpoly[],
int highpoly_num,
size_t pixels_num,
bool is_custom_cage,
float cage_extrusion,
float max_ray_distance,
const float mat_low[4][4],
const float mat_cage[4][4],
struct Mesh *me_cage);
void RE_bake_pixels_populate(struct Mesh *mesh,
struct BakePixel *pixel_array,
size_t pixels_num,
const struct BakeTargets *targets,
StringRef uv_layer);
void RE_bake_mask_fill(const BakePixel pixel_array[], size_t pixels_num, char *mask);
void RE_bake_margin(struct ImBuf *ibuf,
char *mask,
int margin,
char margin_type,
const Mesh *mesh,
StringRef uv_layer,
const float uv_offset[2]);
void RE_bake_normal_world_to_object(const BakePixel pixel_array[],
size_t pixels_num,
int depth,
float result[],
struct Object *ob,
const eBakeNormalSwizzle normal_swizzle[3]);
/**
* This function converts an object space normal map
* to a tangent space normal map for a given low poly mesh.
*/
void RE_bake_normal_world_to_tangent(const BakePixel pixel_array[],
size_t pixels_num,
int depth,
float result[],
struct Mesh *mesh,
const eBakeNormalSwizzle normal_swizzle[3],
const float mat[4][4]);
void RE_bake_normal_world_to_world(const BakePixel pixel_array[],
size_t pixels_num,
int depth,
float result[],
const eBakeNormalSwizzle normal_swizzle[3]);
void RE_bake_ibuf_clear(struct Image *image, bool is_tangent);
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
namespace blender {
namespace compositor {
class RenderContext;
enum class NodeGroupOutputTypes : uint8_t;
} // namespace compositor
struct bNodeTree;
struct Render;
struct Main;
struct RenderData;
struct Scene;
/* ------------------------------------------------------------------------------------------------
* Render Compositor
*
* Implementation of the compositor for final rendering, as opposed to the viewport compositor
* that is part of the draw manager. The input and output of this is pre-existing RenderResult
* buffers in scenes, that are uploaded to and read back from the GPU. */
namespace render {
class Compositor;
}
/* Execute compositor. */
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);
/* Free compositor caches. */
void RE_compositor_free(Render &render);
} // namespace blender

View File

@@ -0,0 +1,294 @@
/* SPDX-FileCopyrightText: 2006 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*/
#pragma once
#include "DNA_listBase.h"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "RE_bake.h"
#include "RNA_types.hh"
#include "BLI_threads.h"
namespace blender {
struct BakePixel;
struct BakeTargets;
struct bNode;
struct bNodeTree;
struct Depsgraph;
struct GPUContext;
struct Main;
struct Object;
struct Render;
struct RenderData;
struct RenderEngine;
struct RenderEngineType;
struct RenderLayer;
struct RenderPass;
struct RenderResult;
struct ReportList;
struct Scene;
struct ViewLayer;
struct ViewRender;
/* External Engine */
/** #RenderEngineType.flag */
enum RenderEngineTypeFlag {
RE_INTERNAL = (1 << 0),
RE_USE_PREVIEW = (1 << 1),
RE_USE_POSTPROCESS = (1 << 2),
RE_USE_EEVEE_VIEWPORT = (1 << 3),
RE_USE_SHADING_NODES_CUSTOM = (1 << 4),
RE_USE_SPHERICAL_STEREO = (1 << 5),
RE_USE_STEREO_VIEWPORT = (1 << 6),
RE_USE_GPU_CONTEXT = (1 << 7),
RE_USE_CUSTOM_FREESTYLE = (1 << 8),
RE_USE_NO_IMAGE_SAVE = (1 << 9),
RE_USE_MATERIALX = (1 << 10),
};
/** #RenderEngine.flag */
enum RenderEngineFlag {
RE_ENGINE_ANIMATION = (1 << 0),
RE_ENGINE_PREVIEW = (1 << 1),
RE_ENGINE_DO_DRAW = (1 << 2),
RE_ENGINE_DO_UPDATE = (1 << 3),
RE_ENGINE_RENDERING = (1 << 4),
RE_ENGINE_HIGHLIGHT_TILES = (1 << 5),
RE_ENGINE_CAN_DRAW = (1 << 6),
};
extern ListBaseT<RenderEngineType> R_engines;
struct RenderEngineType {
struct RenderEngineType *next, *prev;
/* Type info. */
char idname[/*BKE_ST_MAXNAME*/ 64];
char name[64];
int flag;
void (*update)(struct RenderEngine *engine, struct Main *bmain, struct Depsgraph *depsgraph);
void (*render)(struct RenderEngine *engine, struct Depsgraph *depsgraph);
/* Offline rendering is finished - no more view layers will be rendered.
*
* All the pending data is to be communicated from the engine back to Blender. In a possibly
* most memory-efficient manner (engine might free its database before making Blender to allocate
* full-frame render result). */
void (*render_frame_finish)(struct RenderEngine *engine);
void (*draw)(struct RenderEngine *engine,
const struct bContext *context,
struct Depsgraph *depsgraph);
void (*bake)(struct RenderEngine *engine,
struct Depsgraph *depsgraph,
struct Object *object,
int pass_type,
int pass_filter,
int width,
int height);
void (*view_update)(struct RenderEngine *engine,
const struct bContext *context,
struct Depsgraph *depsgraph);
void (*view_draw)(struct RenderEngine *engine,
const struct bContext *context,
struct Depsgraph *depsgraph);
void (*update_script_node)(struct RenderEngine *engine,
struct bNodeTree *ntree,
struct bNode *node);
void (*update_render_passes)(struct RenderEngine *engine,
struct Scene *scene,
struct ViewLayer *view_layer);
void (*update_custom_camera)(struct RenderEngine *engine, struct Camera *cam);
struct DrawEngineType *draw_engine;
/* RNA integration */
ExtensionRNA rna_ext;
};
using update_render_passes_cb_t = void (*)(void *userdata,
struct Scene *scene,
struct ViewLayer *view_layer,
const char *name,
int channels,
const char *chanid,
eNodeSocketDatatype type);
struct RenderEngine {
RenderEngineType *type;
void *py_instance;
int flag;
struct Object *camera_override;
unsigned int layer_override;
struct Render *re;
ListBaseT<RenderResult> fullresult;
char text[/*IMA_MAX_RENDER_TEXT_SIZE*/ 512];
int resolution_x, resolution_y;
struct ReportList *reports;
struct {
const struct BakeTargets *targets;
const struct BakePixel *pixels;
float *result;
int image_id;
int object_id;
} bake;
/* Depsgraph */
struct Depsgraph *depsgraph;
bool has_grease_pencil;
/* callback for render pass query */
ThreadMutex update_render_passes_mutex;
update_render_passes_cb_t update_render_passes_cb;
void *update_render_passes_data;
/* GPU context. */
GHOST_IContext *system_gpu_context; /* WindowManager GPU context -> GHOSTContext. */
ThreadMutex blender_gpu_context_mutex;
bool use_drw_render_context;
struct GPUContext *blender_gpu_context;
/* Whether to restore DRWState after RenderEngine display pass. */
bool gpu_restore_context;
};
RenderEngine *RE_engine_create(RenderEngineType *type);
void RE_engine_free(RenderEngine *engine);
/**
* Loads in image into a result, size must match
* x/y offsets are only used on a partial copy when dimensions don't match.
*/
void RE_layer_load_from_file(
struct RenderLayer *layer, struct ReportList *reports, const char *filepath, int x, int y);
void RE_result_load_from_file(struct RenderResult *result,
struct ReportList *reports,
const char *filepath);
struct RenderResult *RE_engine_begin_result(
RenderEngine *engine, int x, int y, int w, int h, const char *layername, const char *viewname);
void RE_engine_update_result(RenderEngine *engine, struct RenderResult *result);
void RE_engine_add_pass(RenderEngine *engine,
const char *name,
int channels,
const char *chan_id,
const char *layername);
void RE_engine_end_result(RenderEngine *engine,
struct RenderResult *result,
bool cancel,
bool highlight,
bool merge_results);
struct RenderResult *RE_engine_get_result(struct RenderEngine *engine);
struct RenderPass *RE_engine_pass_by_index_get(struct RenderEngine *engine,
const char *layer_name,
int index);
const char *RE_engine_active_view_get(RenderEngine *engine);
void RE_engine_active_view_set(RenderEngine *engine, const char *viewname);
float RE_engine_get_camera_shift_x(RenderEngine *engine,
struct Object *camera,
bool use_spherical_stereo);
void RE_engine_get_camera_model_matrix(RenderEngine *engine,
struct Object *camera,
bool use_spherical_stereo,
float r_modelmat[16]);
bool RE_engine_get_spherical_stereo(RenderEngine *engine, struct Object *camera);
bool RE_engine_test_break(RenderEngine *engine);
void RE_engine_update_stats(RenderEngine *engine, const char *stats, const char *info);
void RE_engine_update_progress(RenderEngine *engine, float progress);
void RE_engine_update_memory_stats(RenderEngine *engine, float mem_used, float mem_peak);
void RE_engine_report(RenderEngine *engine, int type, const char *msg);
void RE_engine_set_error_message(RenderEngine *engine, const char *msg);
bool RE_engine_render(struct Render *re, bool do_all);
bool RE_engine_is_external(const struct Render *re);
void RE_engine_frame_set(struct RenderEngine *engine, int frame, float subframe);
void RE_engine_update_render_passes(struct RenderEngine *engine,
struct Scene *scene,
struct ViewLayer *view_layer,
update_render_passes_cb_t callback,
void *callback_data);
void RE_engine_register_pass(struct RenderEngine *engine,
struct Scene *scene,
struct ViewLayer *view_layer,
const char *name,
int channels,
const char *chanid,
eNodeSocketDatatype type);
bool RE_engine_use_persistent_data(struct RenderEngine *engine);
struct RenderEngine *RE_engine_get(const struct Render *re);
struct RenderEngine *RE_view_engine_get(const struct ViewRender *view_render);
/**
* Acquire render engine for drawing via its `draw()` callback.
*
* If drawing is not possible false is returned. If drawing is possible then the engine is
* "acquired" so that it can not be freed by the render pipeline.
*
* Drawing is possible if the engine has the `draw()` callback and it is in its `render()`
* callback.
*/
bool RE_engine_draw_acquire(struct Render *re);
void RE_engine_draw_release(struct Render *re);
/**
* GPU context for engine to create and update GPU resources in its own thread,
* without blocking the main thread. Used by Cycles' display driver to create
* display textures.
*/
bool RE_engine_gpu_context_create(struct RenderEngine *engine);
void RE_engine_gpu_context_destroy(struct RenderEngine *engine);
bool RE_engine_gpu_context_enable(struct RenderEngine *engine);
void RE_engine_gpu_context_disable(struct RenderEngine *engine);
void RE_engine_gpu_context_lock(struct RenderEngine *engine);
void RE_engine_gpu_context_unlock(struct RenderEngine *engine);
/* Engine Types */
void RE_engines_init();
void RE_engines_exit();
void RE_engines_register(RenderEngineType *render_type);
RenderEngineType *RE_engines_find(const char *idname);
bool RE_engines_is_registered(const char *idname);
const rcti *RE_engine_get_current_tiles(struct Render *re, int *r_total_tiles);
struct RenderData *RE_engine_get_render_data(struct Render *re);
void RE_bake_engine_set_engine_parameters(struct Render *re,
struct Main *bmain,
struct Scene *scene);
void RE_engine_free_blender_memory(struct RenderEngine *engine);
void RE_engine_tile_highlight_set(
struct RenderEngine *engine, int x, int y, int width, int height, bool highlight);
void RE_engine_tile_highlight_clear_all(struct RenderEngine *engine);
} // namespace blender

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2010 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*/
#pragma once
#include "DNA_scene_types.h"
#include "BLI_set.hh"
#include "BLI_vector.hh"
#include "RE_pipeline.h"
namespace blender {
struct Image;
struct Mesh;
struct MultiresBakeRender;
struct MultiresModifierData;
struct MultiresBakeRender {
/* Base mesh at the input of the multiresolution modifier and data of the modifier which is being
* baked. */
Mesh *base_mesh = nullptr;
MultiresModifierData *multires_modifier = nullptr;
int bake_margin = 0;
eBakeMarginType bake_margin_type = R_BAKE_ADJACENT_FACES;
eBakeType type = R_BAKE_NORMALS;
eBakeSpace displacement_space = R_BAKE_SPACE_OBJECT;
/* Use low-resolution mesh when baking displacement maps.
* When true displacement is calculated between the final position in the SubdivCCG and the
* corresponding location on the base mesh.
* When false displacement is calculated between the final position in the SubdivCCG and the
* multiresolution modifier calculated at the bake level, further subdivided (without adding
* displacement) to the final multi-resolution level. */
bool use_low_resolution_mesh = false;
/* Material aligned image array (for per-face bake image), */
Vector<Image *> ob_image;
Set<Image *> images;
int num_total_objects = 0;
int num_baked_objects = 0;
bool *stop = nullptr;
bool *do_update = nullptr;
float *progress = nullptr;
};
void RE_multires_bake_images(MultiresBakeRender &bake);
} // namespace blender

View File

@@ -0,0 +1,512 @@
/* SPDX-FileCopyrightText: 2006 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*/
#pragma once
#include "DNA_ID.h"
#include "DNA_listBase.h"
#include "DNA_vec_types.h"
class GHOST_IContext;
namespace blender {
namespace gpu {
class Texture;
}
struct ExrHandle;
struct ImBuf;
struct Image;
struct ImageFormatData;
struct MovieWriter;
struct Main;
struct Object;
struct RenderData;
struct RenderResult;
struct ReportList;
struct Scene;
struct StampData;
struct ViewLayer;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* this include is what is exposed of render to outside world */
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* length of the scene name + passname */
#define RE_MAXNAME ((MAX_ID_NAME - 2) + 10)
/* only used as handle */
struct RenderView {
struct RenderView *next, *prev;
char name[/*EXR_VIEW_MAXNAME*/ 64];
/**
* Image buffer of a composited layer or a sequencer output.
* The `ibuf` is only allocated if it has an actual data in one of its buffers
* (float, byte, or GPU).
*/
struct ImBuf *ibuf;
};
struct RenderPass {
struct RenderPass *next, *prev;
int channels;
char name[/*EXR_PASS_MAXNAME*/ 64];
char chan_id[/*EXR_PASS_MAXCHAN*/ 24];
/**
* Image buffer which contains data of this pass.
*
* The data can be either CPU side stored in ibuf->float_buffer, or a GPU-side stored in
* ibuf->gpu (during rendering, i.e.).
*
* The pass data storage is lazily allocated, and until data is actually provided
* (via either CPU buffer of GPU texture) the ibuf is not allocated.
*/
struct ImBuf *ibuf;
int rectx, recty;
char fullname[/*EXR_PASS_MAXNAME*/ 64];
char view[/*EXR_VIEW_MAXNAME*/ 64];
/** Quick lookup. */
int view_id;
char _pad0[4];
};
/**
* - A render-layer is a full image, but with all passes and samples.
* - The size of the rects is defined in #RenderResult.
* - After render, the Combined pass is in combined,
* for render-layers read from files it is a real pass.
*/
struct RenderLayer {
struct RenderLayer *next, *prev;
/** copy of RenderData */
char name[RE_MAXNAME];
int layflag, passflag, pass_xor;
int rectx, recty;
ListBaseT<RenderPass> passes;
};
struct RenderResult {
struct RenderResult *next = nullptr, *prev = nullptr;
/* The number of users of this render result. Default value is 0. The result is freed when
* #RE_FreeRenderResult is called with the render result with 0 users. In a way this is
* off-by-one, but it is the easiest for the currently used zero-initialized state. The way to
* think of it is the number of extra users.
*
* TODO: Make it an actual number of users, so the #RE_FreeRenderResult frees the result when
* the number of users goes to 0.
*
* TODO: Make it atomic. Currently it is not to allow shallow copying. */
int user_counter = 0;
/* target image size */
int rectx = 0, recty = 0;
/* The temporary storage to pass image data from #RE_AcquireResultImage.
* Is null pointer when the RenderResult is not coming from the #RE_AcquireResultImage, and is
* a pointer to an existing ibuf in either RenderView or a RenderPass otherwise. */
struct ImBuf *ibuf = nullptr;
/* coordinates within final image (after cropping) */
rcti tilerect;
/* the main buffers */
ListBaseT<RenderLayer> layers = {};
/* multiView maps to a StringVector in OpenEXR */
ListBaseT<RenderView> views = {};
/* Render layer to display. */
RenderLayer *renlay = nullptr;
/* for render results in Image, verify validity for sequences */
int framenr = 0;
/**
* Pixels per meter (for image output).
* - Typically initialized via #BKE_scene_ppm_get.
* - May be zero which indicates the PPM being "unset".
* Although in most cases a scene is available.
*/
double ppm[2];
/* for acquire image, to indicate if it there is a combined layer */
bool have_combined = false;
/* render info text */
char *text = nullptr;
char *error = nullptr;
struct StampData *stamp_data = nullptr;
bool passes_allocated = false;
};
struct RenderStats {
int cfra;
bool localview;
double starttime, lastframetime;
const char *infostr, *statstr;
char scene_name[MAX_ID_NAME - 2];
int mem_used, mem_peak;
};
/* *********************** API ******************** */
/**
* The owner is a unique identifier for the render, either an original scene
* datablock for regular renders, or an area for preview renders.
* Calling a new render with an existing owner frees the existing render. */
struct Render *RE_NewRender(const void *owner);
struct Render *RE_GetRender(const void *owner);
struct Scene;
struct Render *RE_NewSceneRender(const struct Scene *scene);
struct Render *RE_GetSceneRender(const struct Scene *scene);
struct RenderEngineType;
struct ViewRender *RE_NewViewRender(struct RenderEngineType *engine_type);
/* Creates a new render for interactive compositing of the given scene. If an existing render
* exists for the given scene, it is returned instead. See interactive_compositor_renders in
* RenderGlobal for more information. */
struct Render *RE_NewInteractiveCompositorRender(const struct Scene *scene);
/* Assign default dummy callbacks. */
/**
* Use free render as signal to do everything over (previews).
*
* Only call this while you know it will remove the link too.
*/
void RE_FreeRender(struct Render *re);
void RE_FreeViewRender(struct ViewRender *view_render);
/**
* Only called on exit.
*/
void RE_FreeAllRender();
/**
* On file load, free all interactive compositor renders.
*/
void RE_FreeInteractiveCompositorRenders();
/**
* On file load, free render results.
*/
void RE_FreeAllRenderResults();
/**
* On file load or changes engines, free persistent render data.
* Assumes no engines are currently rendering.
*/
void RE_FreeAllPersistentData();
/**
* Free persistent render data, optionally only for the given scene.
*/
void RE_FreePersistentData(const struct Scene *scene);
/**
* Free cached GPU textures to reduce memory usage.
*/
void RE_FreeGPUTextureCaches();
/**
* Free cached GPU textures, contexts and compositor to reduce memory usage,
* when nothing in the UI requires them anymore.
*/
void RE_FreeUnusedGPUResources();
/**
* Get results and statistics.
*/
void RE_FreeRenderResult(struct RenderResult *rr);
/**
* If you want to know exactly what has been done.
*/
struct RenderResult *RE_AcquireResultRead(struct Render *re);
struct RenderResult *RE_AcquireResultWrite(struct Render *re);
void RE_ReferenceRenderResult(struct RenderResult *rr);
void RE_ReleaseResult(struct Render *re);
/**
* Same as #RE_AcquireResultImage but creating the necessary views to store the result
* fill provided result struct with a copy of thew views of what is done so far the
* #RenderResult.views #ListBaseT needs to be freed after with #RE_ReleaseResultImageViews
*/
void RE_AcquireResultImageViews(struct Render *re, struct RenderResult *rr);
/**
* Clear temporary #RenderResult struct.
*/
void RE_ReleaseResultImageViews(struct Render *re, struct RenderResult *rr);
/**
* Fill provided result struct with what's currently active or done.
* This #RenderResult struct is the only exception to the rule of a #RenderResult
* always having at least one #RenderView.
*/
void RE_AcquireResultImage(struct Render *re, struct RenderResult *rr, int view_id);
void RE_ReleaseResultImage(struct Render *re);
void RE_SwapResult(struct Render *re, struct RenderResult **rr);
void RE_ClearResult(struct Render *re);
struct RenderStats *RE_GetStats(struct Render *re);
/**
* Caller is responsible for allocating `dst` in correct size!
*/
void RE_ResultGet32(Render *re, uint8_t *dst);
bool RE_ResultIsMultiView(struct RenderResult *rr);
void RE_render_result_full_channel_name(char *fullname,
const char *layname,
const char *passname,
const char *viewname,
const char *chan_id,
int channel);
struct ImBuf *RE_render_result_rect_to_ibuf(struct RenderResult *rr,
const struct ImageFormatData *imf,
const float dither,
int view_id);
void RE_render_result_rect_from_ibuf(struct RenderResult *rr,
const struct ImBuf *ibuf,
int view_id);
struct RenderLayer *RE_GetRenderLayer(struct RenderResult *rr, const char *name);
float *RE_RenderLayerGetPass(struct RenderLayer *rl, const char *name, const char *viewname);
struct ImBuf *RE_RenderLayerGetPassImBuf(struct RenderLayer *rl,
const char *name,
const char *viewname);
bool RE_HasSingleLayer(struct Render *re);
/**
* Add passes for grease pencil.
* Create a render-layer and render-pass for grease-pencil layer.
*/
struct RenderPass *RE_create_gp_pass(struct RenderResult *rr,
const char *layername,
const char *viewname);
void RE_create_render_pass(struct RenderResult *rr,
const char *name,
int channels,
const char *chan_id,
const char *layername,
const char *viewname,
bool allocate);
/**
* Obligatory initialize call, doesn't change during entire render sequence.
* \param disprect: is optional. if NULL it assumes full window render.
*/
void RE_InitState(struct Render *re,
struct Render *source,
struct RenderData *rd,
ListBaseT<ViewLayer> *render_layers,
struct ViewLayer *single_layer,
int winx,
int winy,
const rcti *disprect);
/**
* Set up the view-plane/perspective matrix, three choices.
*
* \return camera override if set.
*/
struct Object *RE_GetCamera(struct Render *re);
void RE_SetOverrideCamera(struct Render *re, struct Object *cam_ob);
/**
* Per render, there's one persistent view-plane. Parts will set their own view-planes.
*
* \note call this after #RE_InitState().
*/
void RE_SetCamera(struct Render *re, const struct Object *cam_ob);
/**
* Get current view and window transform.
*/
void RE_GetViewPlane(struct Render *re, rctf *r_viewplane, rcti *r_disprect);
/**
* Set the render threads based on the command-line and auto-threads setting.
*/
void RE_init_threadcount(Render *re);
bool RE_WriteRenderViewsMovie(struct ReportList *reports,
struct RenderResult *rr,
struct Scene *scene,
struct RenderData *rd,
struct MovieWriter **movie_writers,
int totvideos,
bool preview);
/**
* General Blender frame render call.
*
* \note Only #RE_NewRender() needed, main Blender render calls.
*
* \param write_still: Saves frames to disk (typically disabled). Useful for batch-operations
* (e.g. rendering from Python) when an additional save action for is inconvenient.
* This is the default behavior for #RE_RenderAnim.
*/
void RE_RenderFrame(struct Render *re,
struct Main *bmain,
struct Scene *scene,
struct ViewLayer *single_layer,
struct Object *camera_override,
int frame,
float subframe,
bool write_still);
/**
* A version of #RE_RenderFrame that saves images to disk.
*/
void RE_RenderAnim(struct Render *re,
struct Main *bmain,
struct Scene *scene,
struct ViewLayer *single_layer,
struct Object *camera_override,
int sfra,
int efra,
int tfra);
#ifdef WITH_FREESTYLE
void RE_RenderFreestyleStrokes(struct Render *re,
struct Main *bmain,
struct Scene *scene,
bool render);
void RE_RenderFreestyleExternal(struct Render *re);
#endif
void RE_SetActiveRenderView(struct Render *re, const char *viewname);
const char *RE_GetActiveRenderView(struct Render *re);
/**
* Error reporting.
*/
void RE_SetReports(struct Render *re, struct ReportList *reports);
/**
* Main preview render call.
*/
void RE_PreviewRender(struct Render *re, struct Main *bmain, struct Scene *scene);
/**
* Only the temp file!
*/
bool RE_ReadRenderResult(struct Scene *scene, struct Scene *scenode);
struct RenderResult *RE_MultilayerConvert(
ExrHandle *exrhandle, const char *colorspace, bool predivide, int rectx, int recty);
/**
* Display, event callbacks and GPU contexts
* */
void RE_display_init(Render *re);
void RE_display_ensure_gpu_context(Render *re);
void RE_display_share(Render *re, const Render *parent_re);
void RE_display_free(Render *re);
void RE_display_update_cb(struct Render *re,
void *handle,
void (*f)(void *handle, RenderResult *rr, struct rcti *rect));
void RE_stats_draw_cb(struct Render *re, void *handle, void (*f)(void *handle, RenderStats *rs));
void RE_progress_cb(struct Render *re, void *handle, void (*f)(void *handle, float));
void RE_draw_lock_cb(struct Render *re, void *handle, void (*f)(void *handle, bool lock));
void RE_test_break_cb(struct Render *re, void *handle, bool (*f)(void *handle));
void RE_prepare_viewlayer_cb(struct Render *re,
void *handle,
bool (*f)(void *handle, ViewLayer *vl, struct Depsgraph *depsgraph));
void RE_current_scene_update_cb(struct Render *re,
void *handle,
void (*f)(void *handle, struct Scene *scene));
GHOST_IContext *RE_system_gpu_context_get(Render *re);
void *RE_blender_gpu_context_ensure(Render *re);
bool RE_seq_render_active(struct Scene *scene, struct RenderData *rd);
/**
* Used in the interface to decide whether to show layers or passes.
*/
bool RE_layers_have_name(struct RenderResult *result);
bool RE_passes_have_name(struct RenderLayer *rl);
struct RenderPass *RE_pass_find_by_name(struct RenderLayer *rl,
const char *name,
const char *viewname);
/**
* Set the buffer data of the render pass.
* The pass takes ownership of the data, and creates an implicit sharing handle to allow its
* sharing with other users.
*/
void RE_pass_set_buffer_data(struct RenderPass *pass, float *data);
/**
* Ensure a GPU texture corresponding to the render buffer data exists.
*/
gpu::Texture *RE_pass_ensure_gpu_texture_cache(struct Render *re, struct RenderPass *rpass);
void RE_GetCameraWindow(struct Render *re, const struct Object *camera, float r_winmat[4][4]);
/**
* Must be called after #RE_GetCameraWindow(), does not change `re->winmat`.
*/
void RE_GetCameraWindowWithOverscan(const struct Render *re, float overscan, float r_winmat[4][4]);
void RE_GetCameraModelMatrix(const struct Render *re,
const struct Object *camera,
float r_modelmat[4][4]);
void RE_GetWindowMatrixWithOverscan(bool is_ortho,
float clip_start,
float clip_end,
rctf viewplane,
float overscan,
float r_winmat[4][4]);
struct Scene *RE_GetScene(struct Render *re);
void RE_SetScene(struct Render *re, struct Scene *sce);
/* When rendering an animation, saving files is required, either through scene saving or through
* a compositor File Output node. */
bool RE_disable_save_output_allowed(const bool is_animation, Scene &scene, ReportList *reports);
bool RE_is_rendering_allowed(const Main &bmain,
struct Scene *scene,
struct ViewLayer *single_layer,
struct Object *camera_override,
struct ReportList *reports);
bool RE_allow_render_generic_object(struct Object *ob);
/******* defined in `render_result.cc` *********/
bool RE_HasCombinedLayer(const RenderResult *result);
bool RE_HasFloatPixels(const RenderResult *result);
bool RE_RenderResult_is_stereo(const RenderResult *result);
struct RenderView *RE_RenderViewGetById(struct RenderResult *rr, int view_id);
struct RenderView *RE_RenderViewGetByName(struct RenderResult *rr, const char *viewname);
RenderResult *RE_DuplicateRenderResult(RenderResult *rr);
struct ImBuf *RE_RenderPassEnsureImBuf(RenderPass *render_pass);
struct ImBuf *RE_RenderViewEnsureImBuf(const RenderResult *render_result, RenderView *render_view);
/* Returns true if the pass is a color (as opposite of data) and needs to be color managed. */
bool RE_RenderPassIsColor(const RenderPass *render_pass);
} // namespace blender

View File

@@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2006 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup render
*
* This include is for non-render pipeline exports (still old cruft here).
*/
#pragma once
#include "BLI_compiler_attrs.h"
/* called by meshtools */
namespace blender {
struct Depsgraph;
struct ImagePool;
struct MTex;
struct Tex;
/* `texture_procedural.cc` */
/**
* \param pool: Thread pool, may be NULL.
*
* \return True if the texture has color, otherwise false.
*/
bool RE_texture_evaluate(const struct MTex *mtex,
const float vec[3],
int thread,
struct ImagePool *pool,
bool skip_load_image,
bool texnode_preview,
/* Return arguments. */
float *r_intensity,
float r_rgba[4]) ATTR_NONNULL(1, 2, 7, 8);
/**
* \param tex: Texture.
* \param out: Previous color.
* \param fact: Texture strength.
* \param facg: Button strength value.
*/
float texture_value_blend(float tex, float out, float fact, float facg, int blendtype);
void RE_texture_rng_init();
void RE_texture_rng_exit();
/* `texture_procedural.cc` */
/**
* Texture evaluation result.
*/
struct TexResult {
float tin;
float trgba[4];
/* Is actually a boolean: When true -> use alpha, false -> set alpha to 1.0. */
int talpha;
};
/* This one uses nodes. */
/**
* WARNING(@ideasman42): if the texres's values are not declared zero,
* check the return value to be sure the color values are set before using the r/g/b values,
* otherwise you may use uninitialized values.
*
* Use it for stuff which is out of render pipeline.
*/
int multitex_ext(struct Tex *tex,
const float texvec[3],
struct TexResult *texres,
short thread,
struct ImagePool *pool,
bool scene_color_manage,
bool skip_load_image);
/**
* Nodes disabled.
* extern-tex doesn't support nodes (#ntreeBeginExec() can't be called when rendering is going on).
*
* Use it for stuff which is out of render pipeline.
*/
int multitex_ext_safe(struct Tex *tex,
const float texvec[3],
struct TexResult *texres,
struct ImagePool *pool,
bool scene_color_manage,
bool skip_load_image);
/**
* Only for internal node usage.
*
* this is called from the shader and texture nodes
* Use it from render pipeline only!
*/
int multitex_nodes(struct Tex *tex,
const float texvec[3],
struct TexResult *texres,
short thread,
short which_output,
const struct MTex *mtex,
struct ImagePool *pool);
} // namespace blender

View File

@@ -0,0 +1,33 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
*/
#include "BLI_string_ref.hh"
namespace blender {
struct ImBuf;
struct Mesh;
/**
* Generate a margin around the textures uv islands by copying pixels from the adjacent polygon.
*
* \param ibuf: the texture image.
* \param mask: pixels with a mask value of 1 are not written to.
* \param margin: the size of the margin in pixels.
* \param me: the mesh to use the polygons of.
* \param uv_layer: The UV layer to use.
*/
void RE_generate_texturemargin_adjacentfaces(struct ImBuf *ibuf,
char *mask,
int margin,
struct Mesh const *me,
StringRef uv_layer,
const float uv_offset[2]);
} // namespace blender

View File

@@ -0,0 +1,104 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Authors
#
# SPDX-License-Identifier: Apache-2.0
# This suppresses the warning "This file includes at least one deprecated or antiquated
# header which may be removed without further notice at a future date", which is caused
# by the USD library including <ext/hash_set> on Linux. This has been reported at:
# https://github.com/PixarAnimationStudios/USD/issues/1057.
if(UNIX AND NOT APPLE)
add_definitions(-D_GLIBCXX_PERMIT_BACKWARD_HASH)
endif()
if(WIN32)
# We need to keep BOOST_DEBUG_PYTHON for the vendored copy of boost inside USD.
add_definitions(-DBOOST_DEBUG_PYTHON)
endif()
# Pre-compiled Linux libraries are made with GCC, and USD uses some extensions
# which lead to an incompatible ABI for Clang. Using those extensions with
# Clang as well works around the issue.
if(UNIX AND NOT APPLE)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
if(DEFINED LIBDIR)
add_definitions(-DARCH_HAS_GNU_STL_EXTENSIONS)
endif()
endif()
endif()
# USD headers use deprecated TBB headers, silence warning.
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
if(WIN32)
# Some USD library headers trigger the "unreferenced formal parameter"
# warning alert.
# Silence them by restore warn C4100 back to w4
remove_c_and_cxx_flag("/w34100")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64")
# USD currently does not support the new preprocessor,
# so we remove it here and disable sse2neon
remove_c_and_cxx_flag("/Zc:preprocessor")
add_definitions(-DDISABLE_SSE2NEON)
endif()
endif()
if(WITH_OPENGL_BACKEND)
add_definitions(-DWITH_OPENGL_BACKEND)
endif()
set(INC
../../../../intern/guardedalloc
../../blenlib
../../makesdna
../../makesrna
../../io/usd
../../gpu/intern
../../python/intern
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
..
)
set(INC_SYS
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::depsgraph
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::clog
bf_io_usd
PRIVATE bf::nodes
PRIVATE bf::dependencies::optional::python
PRIVATE bf::dependencies::optional::usd
PRIVATE bf::dependencies::optional::tbb
PRIVATE bf::dependencies::epoxy
PRIVATE bf::dependencies::gflags
PRIVATE bf::dependencies::eigen
)
set(SRC
camera.cc
engine.cc
final_engine.cc
light_tasks_delegate.cc
preview_engine.cc
python.cc
render_task_delegate.cc
viewport_engine.cc
camera.hh
engine.hh
final_engine.hh
light_tasks_delegate.hh
preview_engine.hh
render_task_delegate.hh
settings.hh
viewport_engine.hh
)
blender_add_lib(bf_render_hydra "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_render_hydra bf_rna)

View File

@@ -0,0 +1,137 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "camera.hh"
#include "BKE_camera.h"
#include "DNA_camera_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "DEG_depsgraph_query.hh"
namespace blender::render::hydra {
static pxr::GfMatrix4d gf_matrix_from_transform(const float m[4][4])
{
pxr::GfMatrix4d ret;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
ret[i][j] = m[i][j];
}
}
return ret;
}
static void gf_camera_fill_dof_data(const Object *camera_obj, pxr::GfCamera *gf_camera)
{
if (camera_obj == nullptr || camera_obj->type != OB_CAMERA) {
return;
}
const Camera *camera = id_cast<Camera *>(camera_obj->data);
if (!(camera->dof.flag & CAM_DOF_ENABLED)) {
return;
}
/* World units. Handles DoF object and value. Object takes precedence. */
const float focus_distance = BKE_camera_object_dof_distance(camera_obj);
gf_camera->SetFocusDistance(focus_distance);
/*
* F-stop is unit-less, however it's a ratio between focal length and aperture diameter.
* The aperture must be in the same unit for correctness.
* Focal length in GfCamera is defined in tenths of a world unit.
*
* Following the logic of USD camera data writer:
* tenth_unit_to_meters = 1 / 10
* tenth_unit_to_millimeters = 1000 * tenth_unit_to_meters = 100
* Scene's units scale is not used for camera's focal length.
*/
gf_camera->SetFStop(camera->dof.aperture_fstop * 100.0);
}
static pxr::GfCamera gf_camera(const CameraParams &params,
const pxr::GfVec2i &res,
const pxr::GfVec4f &border)
{
pxr::GfCamera camera;
camera.SetProjection(params.is_ortho ? pxr::GfCamera::Projection::Orthographic :
pxr::GfCamera::Projection::Perspective);
camera.SetClippingRange(pxr::GfRange1f(params.clip_start, params.clip_end));
camera.SetFocalLength(params.lens);
pxr::GfVec2f b_pos(border[0], border[1]), b_size(border[2], border[3]);
float sensor_size = BKE_camera_sensor_size(params.sensor_fit, params.sensor_x, params.sensor_y);
pxr::GfVec2f sensor_scale = (BKE_camera_sensor_fit(params.sensor_fit, res[0], res[1]) ==
CAMERA_SENSOR_FIT_HOR) ?
pxr::GfVec2f(1.0f, float(res[1]) / res[0]) :
pxr::GfVec2f(float(res[0]) / res[1], 1.0f);
pxr::GfVec2f aperture = pxr::GfVec2f((params.is_ortho) ? params.ortho_scale : sensor_size);
aperture = pxr::GfCompMult(aperture, sensor_scale);
aperture = pxr::GfCompMult(aperture, b_size);
aperture *= params.zoom;
if (params.is_ortho) {
/* Use tenths of a world unit according to USD docs
* https://graphics.pixar.com/usd/docs/api/class_gf_camera.html */
aperture *= 10.0f;
}
camera.SetHorizontalAperture(aperture[0]);
camera.SetVerticalAperture(aperture[1]);
pxr::GfVec2f lens_shift = pxr::GfVec2f(params.shiftx, params.shifty);
lens_shift = pxr::GfCompDiv(lens_shift, sensor_scale);
lens_shift += pxr::GfVec2f(params.offsetx, params.offsety);
lens_shift += b_pos + b_size * 0.5f - pxr::GfVec2f(0.5f);
lens_shift = pxr::GfCompDiv(lens_shift, b_size);
camera.SetHorizontalApertureOffset(lens_shift[0] * aperture[0]);
camera.SetVerticalApertureOffset(lens_shift[1] * aperture[1]);
return camera;
}
pxr::GfCamera gf_camera(const Depsgraph *depsgraph,
const View3D *v3d,
const ARegion *region,
const pxr::GfVec4f &border)
{
const RegionView3D *region_data = static_cast<const RegionView3D *>(region->regiondata);
const Scene *scene = DEG_get_evaluated_scene(depsgraph);
CameraParams params;
BKE_camera_params_init(&params);
BKE_camera_params_from_view3d(&params, depsgraph, v3d, region_data);
pxr::GfCamera camera = gf_camera(params, pxr::GfVec2i(region->winx, region->winy), border);
camera.SetTransform(gf_matrix_from_transform(region_data->viewmat).GetInverse());
/* Ensure viewport is in active camera view mode. */
if (region_data->persp == RV3D_CAMOB) {
gf_camera_fill_dof_data(scene->camera, &camera);
}
return camera;
}
pxr::GfCamera gf_camera(const Object *camera_obj,
const pxr::GfVec2i &res,
const pxr::GfVec4f &border)
{
CameraParams params;
BKE_camera_params_init(&params);
BKE_camera_params_from_object(&params, camera_obj);
pxr::GfCamera camera = gf_camera(params, res, border);
camera.SetTransform(gf_matrix_from_transform(camera_obj->object_to_world().ptr()));
gf_camera_fill_dof_data(camera_obj, &camera);
return camera;
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <pxr/base/gf/camera.h>
#include <pxr/base/gf/vec2f.h>
namespace blender {
struct ARegion;
struct Depsgraph;
struct Object;
struct View3D;
namespace render::hydra {
pxr::GfCamera gf_camera(const Depsgraph *depsgraph,
const View3D *v3d,
const ARegion *region,
const pxr::GfVec4f &border);
pxr::GfCamera gf_camera(const Object *camera_obj,
const pxr::GfVec2i &res,
const pxr::GfVec4f &border);
} // namespace render::hydra
} // namespace blender

View File

@@ -0,0 +1,180 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "engine.hh"
#include <pxr/base/plug/plugin.h>
#include <pxr/base/plug/registry.h>
#include <pxr/imaging/hd/flatteningSceneIndex.h>
#include <pxr/imaging/hd/rendererPluginRegistry.h>
#include <pxr/imaging/hdSt/renderDelegate.h>
#include <pxr/imaging/hdsi/extComputationPrimvarPruningSceneIndex.h>
#include <pxr/imaging/hgi/tokens.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usdImaging/usdImaging/flattenedDataSourceProviders.h>
#include <pxr/usdImaging/usdImaging/materialBindingsResolvingSceneIndex.h>
#include "BKE_context.hh"
#include "GPU_context.hh"
#include "DEG_depsgraph_query.hh"
#include "RE_engine.h"
#include "CLG_log.h"
namespace blender::render::hydra {
CLG_LOGREF_DECLARE_GLOBAL(LOG_HYDRA_RENDER, "hydra.render");
Engine::Engine(RenderEngine *bl_engine, const std::string &render_delegate_name)
: render_delegate_name_(render_delegate_name), bl_engine_(bl_engine)
{
pxr::HdRendererPluginRegistry &registry = pxr::HdRendererPluginRegistry::GetInstance();
pxr::TF_PY_ALLOW_THREADS_IN_SCOPE();
pxr::HdDriverVector hd_drivers;
if (bl_engine->type->flag & RE_USE_GPU_CONTEXT) {
pxr::TfToken hgi_token;
switch (GPU_backend_get_type()) {
case GPU_BACKEND_METAL:
hgi_token = pxr::TfToken("Metal");
break;
case GPU_BACKEND_OPENGL:
hgi_token = pxr::TfToken("OpenGL");
break;
case GPU_BACKEND_VULKAN:
hgi_token = pxr::TfToken("Vulkan");
break;
case GPU_BACKEND_NONE:
case GPU_BACKEND_ANY:
/* When pxr::Hgi::CreateNamedHgi is called with an empty token it will select the default
* platform Hgi. */
break;
}
hgi_ = pxr::Hgi::CreateNamedHgi(hgi_token);
hgi_driver_.name = pxr::HgiTokens->renderDriver;
hgi_driver_.driver = pxr::VtValue(hgi_.get());
hd_drivers.push_back(&hgi_driver_);
}
render_delegate_ = registry.CreateRenderDelegate(pxr::TfToken(render_delegate_name_));
if (!render_delegate_) {
throw std::runtime_error("Cannot create render delegate: " + render_delegate_name_);
}
render_index_.reset(pxr::HdRenderIndex::New(render_delegate_.Get(), hd_drivers));
free_camera_delegate_ = std::make_unique<io::hydra::CameraDelegate>(
render_index_.get(), pxr::SdfPath::AbsoluteRootPath().AppendElementString("freeCamera"));
/* Tasks are exposed as task prims through a retained scene index. */
const bool needs_prefixing = false;
task_scene_index_ = pxr::HdRetainedSceneIndex::New();
render_index_->InsertSceneIndex(
task_scene_index_, pxr::SdfPath::AbsoluteRootPath(), needs_prefixing);
if (bl_engine->type->flag & RE_USE_GPU_CONTEXT && GPU_backend_get_type() == GPU_BACKEND_OPENGL) {
render_task_delegate_ = std::make_unique<GPURenderTaskDelegate>(
render_index_.get(),
task_scene_index_,
pxr::SdfPath::AbsoluteRootPath().AppendElementString("renderTask"));
}
else {
render_task_delegate_ = std::make_unique<RenderTaskDelegate>(
render_index_.get(),
task_scene_index_,
pxr::SdfPath::AbsoluteRootPath().AppendElementString("renderTask"));
}
render_task_delegate_->set_camera(free_camera_delegate_->GetCameraId());
if (render_delegate_name_ == "HdStormRendererPlugin") {
light_tasks_delegate_ = std::make_unique<LightTasksDelegate>(
render_index_.get(),
task_scene_index_,
pxr::SdfPath::AbsoluteRootPath().AppendElementString("lightTasks"));
light_tasks_delegate_->set_camera(free_camera_delegate_->GetCameraId());
}
engine_ = std::make_unique<pxr::HdEngine>();
}
void Engine::sync(Depsgraph *depsgraph, bContext *context)
{
depsgraph_ = depsgraph;
context_ = context;
scene_ = DEG_get_evaluated_scene(depsgraph);
const bool use_materialx = bl_engine_->type->flag & RE_USE_MATERIALX;
if (scene_->hydra.export_method == SCE_HYDRA_EXPORT_HYDRA) {
/* Fast path. */
usd_scene_delegate_.reset();
if (!hydra_scene_index_) {
hydra_scene_index_path_ = pxr::SdfPath::AbsoluteRootPath().AppendElementString("scene");
hydra_scene_index_ = std::make_unique<io::hydra::HydraSceneIndex>(
hydra_scene_index_path_, render_delegate_.Get(), use_materialx);
const bool needs_prefixing = false;
pxr::HdSceneIndexBaseRefPtr filtered = hydra_scene_index_->retained();
filtered = pxr::HdSiExtComputationPrimvarPruningSceneIndex::New(filtered);
filtered = pxr::HdFlatteningSceneIndex::New(filtered,
pxr::UsdImagingFlattenedDataSourceProviders());
filtered = pxr::UsdImagingMaterialBindingsResolvingSceneIndex::New(filtered, nullptr);
render_index_->InsertSceneIndex(filtered, hydra_scene_index_path_, needs_prefixing);
}
hydra_scene_index_->populate(depsgraph, context ? CTX_wm_view3d(context) : nullptr);
}
else {
/* Slow USD export for reference. */
if (hydra_scene_index_) {
hydra_scene_index_->clear();
}
if (!usd_scene_delegate_) {
pxr::SdfPath scene_path = pxr::SdfPath::AbsoluteRootPath().AppendElementString("usd_scene");
usd_scene_delegate_ = std::make_unique<io::hydra::USDSceneIndex>(
render_index_.get(), scene_path, use_materialx);
}
usd_scene_delegate_->populate(depsgraph);
}
free_camera_delegate_->sync(scene_);
}
void Engine::set_render_setting(const std::string &key, const pxr::VtValue &val)
{
render_delegate_->SetRenderSetting(pxr::TfToken(key), val);
}
float Engine::renderer_percent_done()
{
pxr::VtDictionary render_stats = render_delegate_->GetRenderStats();
auto it = render_stats.find("percentDone");
if (it == render_stats.end()) {
return 0.0f;
}
return float(it->second.UncheckedGet<double>());
}
pxr::HdTaskSharedPtrVector Engine::tasks()
{
pxr::HdTaskSharedPtrVector res;
if (light_tasks_delegate_) {
if (scene_->r.alphamode != R_ALPHAPREMUL) {
#ifndef __APPLE__
/* TODO: Temporary disable skydome task for MacOS due to crash with error:
* Failed to create pipeline state, error depthAttachmentPixelFormat is not valid
* and shader writes to depth */
res.push_back(light_tasks_delegate_->skydome_task());
#endif
}
res.push_back(light_tasks_delegate_->simple_task());
}
res.push_back(render_task_delegate_->task());
return res;
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <memory>
#include <string>
#include <pxr/imaging/hd/driver.h>
#include <pxr/imaging/hd/engine.h>
#include <pxr/imaging/hd/pluginRenderDelegateUniqueHandle.h>
#include <pxr/imaging/hd/retainedSceneIndex.h>
#include <pxr/imaging/hgi/hgi.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usdImaging/usdImaging/delegate.h>
#include "hydra/camera_delegate.hh"
#include "hydra/scene_index.hh"
#include "hydra/settings.hh"
#include "hydra/usd_scene_index.hh"
#include "light_tasks_delegate.hh"
#include "render_task_delegate.hh"
struct CLG_LogRef;
namespace blender {
struct bContext;
struct RenderEngine;
namespace render::hydra {
extern CLG_LogRef *LOG_HYDRA_RENDER;
class Engine {
protected:
std::string render_delegate_name_;
RenderEngine *bl_engine_ = nullptr;
Depsgraph *depsgraph_ = nullptr;
bContext *context_ = nullptr;
Scene *scene_ = nullptr;
/* The order is important due to deletion order */
pxr::HgiUniquePtr hgi_;
pxr::HdDriver hgi_driver_;
pxr::HdPluginRenderDelegateUniqueHandle render_delegate_;
std::unique_ptr<pxr::HdRenderIndex> render_index_;
std::unique_ptr<io::hydra::CameraDelegate> free_camera_delegate_;
std::unique_ptr<io::hydra::HydraSceneIndex> hydra_scene_index_;
pxr::SdfPath hydra_scene_index_path_;
std::unique_ptr<io::hydra::USDSceneIndex> usd_scene_delegate_;
pxr::HdRetainedSceneIndexRefPtr task_scene_index_;
std::unique_ptr<RenderTaskDelegate> render_task_delegate_;
std::unique_ptr<LightTasksDelegate> light_tasks_delegate_;
std::unique_ptr<pxr::HdEngine> engine_;
public:
Engine(RenderEngine *bl_engine, const std::string &render_delegate_name);
virtual ~Engine() = default;
void sync(Depsgraph *depsgraph, bContext *context);
virtual void render() = 0;
virtual void set_render_setting(const std::string &key, const pxr::VtValue &val);
protected:
float renderer_percent_done();
pxr::HdTaskSharedPtrVector tasks();
virtual void notify_status(float progress,
const std::string &title,
const std::string &info) = 0;
};
} // namespace render::hydra
} // namespace blender

View File

@@ -0,0 +1,140 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "final_engine.hh"
#include "camera.hh"
#include <pxr/imaging/hd/light.h>
#include <pxr/imaging/hd/renderBuffer.h>
#include "DNA_layer_types.h"
#include "DNA_scene_types.h"
#include "BLI_listbase.h"
#include "BLI_time.h"
#include "BLI_timecode.h"
#include "BKE_lib_id.hh"
#include "DEG_depsgraph_query.hh"
#include "IMB_imbuf_types.hh"
#include "RE_engine.h"
namespace blender::render::hydra {
void FinalEngine::render()
{
const ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph_);
char scene_name[MAX_ID_FULL_NAME];
BKE_id_full_name_get(scene_name, &scene_->id, 0);
const RenderData &r = scene_->r;
pxr::GfVec4f border(0, 0, 1, 1);
if (r.mode & R_BORDER) {
border.Set(r.border.xmin,
r.border.ymin,
r.border.xmax - r.border.xmin,
r.border.ymax - r.border.ymin);
}
pxr::GfVec2i image_res(r.xsch * r.size / 100, r.ysch * r.size / 100);
int width = image_res[0] * border[2];
int height = image_res[1] * border[3];
pxr::GfCamera camera = gf_camera(scene_->camera, image_res, border);
free_camera_delegate_->SetCamera(camera);
render_task_delegate_->set_viewport(pxr::GfVec4d(0, 0, width, height));
if (light_tasks_delegate_) {
light_tasks_delegate_->set_viewport(pxr::GfVec4d(0, 0, width, height));
}
RenderResult *rr = RE_engine_get_result(bl_engine_);
RenderLayer *rlayer = static_cast<RenderLayer *>(rr->layers.first);
for (RenderPass &rpass : rlayer->passes) {
pxr::TfToken *aov_token = aov_tokens_.lookup_ptr(rpass.name);
if (!aov_token) {
CLOG_WARN(LOG_HYDRA_RENDER, "Couldn't find AOV token for render pass: %s", rpass.name);
continue;
}
render_task_delegate_->add_aov(*aov_token);
}
if (bl_engine_->type->flag & RE_USE_GPU_CONTEXT) {
/* For GPU context engine color and depth AOVs has to be added anyway */
render_task_delegate_->add_aov(pxr::HdAovTokens->color);
render_task_delegate_->add_aov(pxr::HdAovTokens->depth);
}
render_task_delegate_->bind();
auto t = tasks();
char elapsed_time[32];
double time_begin = BLI_time_now_seconds();
float percent_done = 0.0;
while (true) {
engine_->Execute(render_index_.get(), &t);
if (RE_engine_test_break(bl_engine_)) {
break;
}
percent_done = renderer_percent_done();
BLI_timecode_string_from_time_simple(
elapsed_time, sizeof(elapsed_time), BLI_time_now_seconds() - time_begin);
notify_status(percent_done / 100.0,
std::string(scene_name) + ": " + view_layer->name,
std::string("Render Time: ") + elapsed_time +
" | Done: " + std::to_string(int(percent_done)) + "%");
if (render_task_delegate_->is_converged()) {
break;
}
update_render_result(width, height, view_layer->name);
}
update_render_result(width, height, view_layer->name);
render_task_delegate_->unbind();
}
void FinalEngine::set_render_setting(const std::string &key, const pxr::VtValue &val)
{
if (STRPREFIX(key.c_str(), "aovToken:")) {
aov_tokens_.add_overwrite(key.substr(key.find(":") + 1),
pxr::TfToken(val.UncheckedGet<std::string>()));
return;
}
Engine::set_render_setting(key, val);
}
void FinalEngine::notify_status(float progress, const std::string &title, const std::string &info)
{
RE_engine_update_progress(bl_engine_, progress);
RE_engine_update_stats(bl_engine_, title.c_str(), info.c_str());
}
void FinalEngine::update_render_result(int width, int height, const char *layer_name)
{
RenderResult *rr = RE_engine_begin_result(bl_engine_, 0, 0, width, height, layer_name, nullptr);
RenderLayer *rlayer = static_cast<RenderLayer *>(
BLI_findstring(&rr->layers, layer_name, offsetof(RenderLayer, name)));
if (rlayer) {
for (RenderPass &rpass : rlayer->passes) {
pxr::TfToken *aov_token = aov_tokens_.lookup_ptr(rpass.name);
if (aov_token) {
render_task_delegate_->read_aov(*aov_token, rpass.ibuf->float_data_for_write());
}
}
}
RE_engine_end_result(bl_engine_, rr, false, false, false);
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "engine.hh"
namespace blender::render::hydra {
class FinalEngine : public Engine {
private:
Map<std::string, pxr::TfToken> aov_tokens_;
public:
using Engine::Engine;
void render() override;
void set_render_setting(const std::string &key, const pxr::VtValue &val) override;
protected:
void notify_status(float progress, const std::string &title, const std::string &info) override;
private:
void update_render_result(int width, int height, const char *layer_name);
};
} // namespace blender::render::hydra

View File

@@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "light_tasks_delegate.hh"
#include "engine.hh"
#include <pxr/imaging/hd/legacyTaskFactory.h>
#include <pxr/imaging/hd/legacyTaskSchema.h>
#include <pxr/imaging/hd/retainedDataSource.h>
#include <pxr/imaging/hd/sceneIndexObserver.h>
#include <pxr/imaging/hd/tokens.h>
namespace blender::render::hydra {
LightTasksDelegate::LightTasksDelegate(pxr::HdRenderIndex *render_index,
pxr::HdRetainedSceneIndexRefPtr task_scene_index,
pxr::SdfPath const &base_id)
: render_index_(render_index),
task_scene_index_(std::move(task_scene_index)),
simple_task_id_(base_id.AppendElementString("simpleTask")),
skydome_task_id_(base_id.AppendElementString("skydomeTask"))
{
publish_simple_task();
publish_skydome_task();
CLOG_DEBUG(LOG_HYDRA_RENDER, "%s", simple_task_id_.GetText());
CLOG_DEBUG(LOG_HYDRA_RENDER, "%s", skydome_task_id_.GetText());
}
void LightTasksDelegate::publish_simple_task()
{
pxr::HdContainerDataSourceHandle task_ds =
pxr::HdLegacyTaskSchema::Builder()
.SetFactory(
pxr::HdRetainedTypedSampledDataSource<pxr::HdLegacyTaskFactorySharedPtr>::New(
pxr::HdMakeLegacyTaskFactory<pxr::HdxSimpleLightTask>()))
.SetParameters(simple_task_params_ds_)
.Build();
task_scene_index_->AddPrims({{simple_task_id_,
pxr::HdPrimTypeTokens->task,
pxr::HdRetainedContainerDataSource::New(
pxr::HdLegacyTaskSchema::GetSchemaToken(), task_ds)}});
}
void LightTasksDelegate::publish_skydome_task()
{
const pxr::HdRprimCollection collection(pxr::HdTokens->geometry,
pxr::HdReprSelector(pxr::HdReprTokens->smoothHull));
const pxr::TfTokenVector render_tags = {pxr::HdRenderTagTokens->geometry};
pxr::HdContainerDataSourceHandle task_ds =
pxr::HdLegacyTaskSchema::Builder()
.SetFactory(
pxr::HdRetainedTypedSampledDataSource<pxr::HdLegacyTaskFactorySharedPtr>::New(
pxr::HdMakeLegacyTaskFactory<pxr::HdxSkydomeTask>()))
.SetParameters(skydome_task_params_ds_)
.SetCollection(
pxr::HdRetainedTypedSampledDataSource<pxr::HdRprimCollection>::New(collection))
.SetRenderTags(
pxr::HdRetainedTypedSampledDataSource<pxr::TfTokenVector>::New(render_tags))
.Build();
task_scene_index_->AddPrims({{skydome_task_id_,
pxr::HdPrimTypeTokens->task,
pxr::HdRetainedContainerDataSource::New(
pxr::HdLegacyTaskSchema::GetSchemaToken(), task_ds)}});
}
pxr::HdTaskSharedPtr LightTasksDelegate::simple_task()
{
return render_index_->GetTask(simple_task_id_);
}
pxr::HdTaskSharedPtr LightTasksDelegate::skydome_task()
{
/* Note that this task is intended to be the first "Render Task",
* so that the AOV's are properly cleared, however it
* does not spawn a HdRenderPass. */
return render_index_->GetTask(skydome_task_id_);
}
void LightTasksDelegate::set_camera(pxr::SdfPath const &camera_id)
{
if (simple_task_params_.cameraPath == camera_id && skydome_task_params_.camera == camera_id) {
return;
}
simple_task_params_.cameraPath = camera_id;
skydome_task_params_.camera = camera_id;
task_scene_index_->DirtyPrims(
{{simple_task_id_, pxr::HdLegacyTaskSchema::GetParametersLocator()},
{skydome_task_id_, pxr::HdLegacyTaskSchema::GetParametersLocator()}});
}
void LightTasksDelegate::set_viewport(pxr::GfVec4d const &viewport)
{
if (skydome_task_params_.viewport == viewport) {
return;
}
skydome_task_params_.viewport = viewport;
task_scene_index_->DirtyPrims(
{{skydome_task_id_, pxr::HdLegacyTaskSchema::GetParametersLocator()}});
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <pxr/imaging/hd/dataSource.h>
#include <pxr/imaging/hd/renderIndex.h>
#include <pxr/imaging/hd/retainedSceneIndex.h>
#include <pxr/imaging/hd/task.h>
#include <pxr/imaging/hdx/simpleLightTask.h>
#include <pxr/imaging/hdx/skydomeTask.h>
#include "render_task_delegate.hh"
namespace blender::render::hydra {
/* Registers a HdxSimpleLightTask and HdxSkydomeTask with the render index. */
class SimpleLightTaskParamsDataSource final
: public pxr::HdTypedSampledDataSource<pxr::HdxSimpleLightTaskParams> {
public:
HD_DECLARE_DATASOURCE(SimpleLightTaskParamsDataSource);
pxr::HdxSimpleLightTaskParams params;
pxr::VtValue GetValue(Time /*t*/) override
{
return pxr::VtValue(params);
}
pxr::HdxSimpleLightTaskParams GetTypedValue(Time /*t*/) override
{
return params;
}
bool GetContributingSampleTimesForInterval(Time /*start*/,
Time /*end*/,
std::vector<Time> * /*sampleTimes*/) override
{
return false;
}
};
class LightTasksDelegate {
public:
LightTasksDelegate(pxr::HdRenderIndex *render_index,
pxr::HdRetainedSceneIndexRefPtr task_scene_index,
pxr::SdfPath const &base_id);
pxr::HdTaskSharedPtr simple_task();
pxr::HdTaskSharedPtr skydome_task();
void set_camera(pxr::SdfPath const &camera_id);
void set_viewport(pxr::GfVec4d const &viewport);
private:
void publish_simple_task();
void publish_skydome_task();
pxr::HdRenderIndex *render_index_ = nullptr;
pxr::HdRetainedSceneIndexRefPtr task_scene_index_;
pxr::SdfPath simple_task_id_;
pxr::SdfPath skydome_task_id_;
SimpleLightTaskParamsDataSource::Handle simple_task_params_ds_ =
SimpleLightTaskParamsDataSource::New();
RenderTaskParamsDataSource::Handle skydome_task_params_ds_ = RenderTaskParamsDataSource::New();
pxr::HdxSimpleLightTaskParams &simple_task_params_ = simple_task_params_ds_->params;
pxr::HdxRenderTaskParams &skydome_task_params_ = skydome_task_params_ds_->params;
};
} // namespace blender::render::hydra

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "preview_engine.hh"
namespace blender::render::hydra {
void PreviewEngine::notify_status(float /*progress*/,
const std::string & /*title*/,
const std::string & /*info*/)
{
/* Empty function. */
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "final_engine.hh"
namespace blender::render::hydra {
class PreviewEngine : public FinalEngine {
public:
using FinalEngine::FinalEngine;
protected:
void notify_status(float progress, const std::string &title, const std::string &info) override;
};
} // namespace blender::render::hydra

View File

@@ -0,0 +1,217 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "final_engine.hh"
#include "preview_engine.hh"
#include "viewport_engine.hh"
#include <Python.h>
#include "RE_engine.h"
#include "../generic/py_capi_utils.hh"
#include "bpy_rna.hh"
#include "BKE_context.hh"
#include "RNA_prototypes.hh"
#include "hydra/image.hh"
namespace blender {
namespace render::hydra {
template<typename T> T *pyrna_to_pointer(PyObject *pyobject, const StructRNA *rnatype)
{
const PointerRNA *ptr = pyrna_struct_as_ptr_or_null(pyobject, rnatype);
return (ptr) ? static_cast<T *>(ptr->data) : nullptr;
}
static PyObject *engine_create_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
char *engine_type, *render_delegate_id;
if (!PyArg_ParseTuple(args, "Oss", &pyengine, &engine_type, &render_delegate_id)) {
return nullptr;
}
RenderEngine *bl_engine = pyrna_to_pointer<RenderEngine>(pyengine, RNA_RenderEngine);
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %s", engine_type);
Engine *engine = nullptr;
try {
if (STREQ(engine_type, "VIEWPORT")) {
engine = new ViewportEngine(bl_engine, render_delegate_id);
}
else if (STREQ(engine_type, "PREVIEW")) {
engine = new PreviewEngine(bl_engine, render_delegate_id);
}
else {
engine = new FinalEngine(bl_engine, render_delegate_id);
}
}
catch (std::runtime_error &e) {
CLOG_ERROR(LOG_HYDRA_RENDER, "%s", e.what());
}
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %p", engine);
return PyLong_FromVoidPtr(engine);
}
static PyObject *engine_free_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
if (!PyArg_ParseTuple(args, "O", &pyengine)) {
return nullptr;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %p", engine);
delete engine;
Py_RETURN_NONE;
}
static PyObject *engine_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine, *pydepsgraph, *pycontext;
if (!PyArg_ParseTuple(args, "OOO", &pyengine, &pydepsgraph, &pycontext)) {
return nullptr;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
Depsgraph *depsgraph = pyrna_to_pointer<Depsgraph>(pydepsgraph, RNA_Depsgraph);
bContext *context = pyrna_to_pointer<bContext>(pycontext, RNA_Context);
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %p", engine);
engine->sync(depsgraph, context);
Py_RETURN_NONE;
}
static PyObject *engine_render_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine;
if (!PyArg_ParseTuple(args, "O", &pyengine)) {
return nullptr;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %p", engine);
/* Allow Blender to execute other Python scripts. */
Py_BEGIN_ALLOW_THREADS;
engine->render();
Py_END_ALLOW_THREADS;
Py_RETURN_NONE;
}
static PyObject *engine_view_draw_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine, *pycontext;
if (!PyArg_ParseTuple(args, "OO", &pyengine, &pycontext)) {
return nullptr;
}
ViewportEngine *engine = static_cast<ViewportEngine *>(PyLong_AsVoidPtr(pyengine));
bContext *context = pyrna_to_pointer<bContext>(pycontext, RNA_Context);
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %p", engine);
/* Allow Blender to execute other Python scripts. */
Py_BEGIN_ALLOW_THREADS;
engine->render(context);
Py_END_ALLOW_THREADS;
Py_RETURN_NONE;
}
static pxr::VtValue get_setting_val(PyObject *pyval)
{
pxr::VtValue val;
if (PyBool_Check(pyval)) {
val = Py_IsTrue(pyval);
}
else if (PyLong_Check(pyval)) {
val = PyLong_AsLong(pyval);
}
else if (PyFloat_Check(pyval)) {
val = PyFloat_AsDouble(pyval);
}
else if (PyUnicode_Check(pyval)) {
val = std::string(PyUnicode_AsUTF8(pyval));
}
return val;
}
static PyObject *engine_set_render_setting_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyengine, *pyval;
char *key;
if (!PyArg_ParseTuple(args, "OsO", &pyengine, &key, &pyval)) {
return nullptr;
}
Engine *engine = static_cast<Engine *>(PyLong_AsVoidPtr(pyengine));
CLOG_DEBUG(LOG_HYDRA_RENDER, "Engine %p: %s", engine, key);
engine->set_render_setting(key, get_setting_val(pyval));
Py_RETURN_NONE;
}
static PyObject *cache_or_get_image_file_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pycontext, *pyimage;
if (!PyArg_ParseTuple(args, "OO", &pycontext, &pyimage)) {
return nullptr;
}
bContext *context = static_cast<bContext *>(PyLong_AsVoidPtr(pycontext));
Image *image = static_cast<Image *>(PyLong_AsVoidPtr(pyimage));
std::string image_path = io::hydra::cache_or_get_image_file(
CTX_data_main(context), CTX_data_scene(context), image, nullptr);
return PyC_UnicodeFromStdStr(image_path);
}
static PyMethodDef methods[] = {
{"engine_create", engine_create_func, METH_VARARGS, ""},
{"engine_free", engine_free_func, METH_VARARGS, ""},
{"engine_update", engine_update_func, METH_VARARGS, ""},
{"engine_render", engine_render_func, METH_VARARGS, ""},
{"engine_view_draw", engine_view_draw_func, METH_VARARGS, ""},
{"engine_set_render_setting", engine_set_render_setting_func, METH_VARARGS, ""},
{"cache_or_get_image_file", cache_or_get_image_file_func, METH_VARARGS, ""},
{nullptr, nullptr, 0, nullptr},
};
static PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"_bpy_hydra",
"Hydra render API",
-1,
methods,
nullptr,
nullptr,
nullptr,
nullptr,
};
} // namespace render::hydra
PyObject *BPyInit_hydra();
PyObject *BPyInit_hydra()
{
PyObject *mod = PyModule_Create(&render::hydra::module);
return mod;
}
} // namespace blender

View File

@@ -0,0 +1,352 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "render_task_delegate.hh"
#ifdef WITH_OPENGL_BACKEND
# include "GPU_context.hh"
# include <epoxy/gl.h>
#endif
#include <pxr/imaging/hd/legacyTaskFactory.h>
#include <pxr/imaging/hd/legacyTaskSchema.h>
#include <pxr/imaging/hd/renderBuffer.h>
#include <pxr/imaging/hd/renderBufferSchema.h>
#include <pxr/imaging/hd/renderDelegate.h>
#include <pxr/imaging/hd/retainedDataSource.h>
#include <pxr/imaging/hd/sceneIndexObserver.h>
#include <pxr/imaging/hd/tokens.h>
#include <pxr/imaging/hdx/renderTask.h>
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include <Eigen/Core>
#include "engine.hh"
namespace blender::render::hydra {
RenderTaskDelegate::RenderTaskDelegate(pxr::HdRenderIndex *render_index,
pxr::HdRetainedSceneIndexRefPtr task_scene_index,
pxr::SdfPath const &base_id)
: render_index_(render_index),
task_scene_index_(std::move(task_scene_index)),
base_id_(base_id),
task_id_(base_id.AppendElementString("task"))
{
task_params_.enableLighting = true;
task_params_.alphaThreshold = 0.1f;
/* Disable this so Metal and OpenGL match in Storm render tests, only
* the former seems to use multisample. */
task_params_.useAovMultiSample = false;
publish_task();
CLOG_DEBUG(LOG_HYDRA_RENDER, "%s", task_id_.GetText());
}
void RenderTaskDelegate::publish_task()
{
const pxr::HdRprimCollection collection(pxr::HdTokens->geometry,
pxr::HdReprSelector(pxr::HdReprTokens->smoothHull));
const pxr::TfTokenVector render_tags = {pxr::HdRenderTagTokens->geometry};
pxr::HdContainerDataSourceHandle task_ds =
pxr::HdLegacyTaskSchema::Builder()
.SetFactory(
pxr::HdRetainedTypedSampledDataSource<pxr::HdLegacyTaskFactorySharedPtr>::New(
pxr::HdMakeLegacyTaskFactory<pxr::HdxRenderTask>()))
.SetParameters(task_params_ds_)
.SetCollection(
pxr::HdRetainedTypedSampledDataSource<pxr::HdRprimCollection>::New(collection))
.SetRenderTags(
pxr::HdRetainedTypedSampledDataSource<pxr::TfTokenVector>::New(render_tags))
.Build();
task_scene_index_->AddPrims({{task_id_,
pxr::HdPrimTypeTokens->task,
pxr::HdRetainedContainerDataSource::New(
pxr::HdLegacyTaskSchema::GetSchemaToken(), task_ds)}});
}
void RenderTaskDelegate::dirty_task_params()
{
task_scene_index_->DirtyPrims({{task_id_, pxr::HdLegacyTaskSchema::GetParametersLocator()}});
}
void RenderTaskDelegate::publish_buffer(pxr::SdfPath const &buf_id,
pxr::HdRenderBufferDescriptor const &desc)
{
pxr::HdContainerDataSourceHandle buffer_ds =
pxr::HdRenderBufferSchema::Builder()
.SetDimensions(pxr::HdRetainedTypedSampledDataSource<pxr::GfVec3i>::New(desc.dimensions))
.SetFormat(pxr::HdRetainedTypedSampledDataSource<pxr::HdFormat>::New(desc.format))
.SetMultiSampled(pxr::HdRetainedTypedSampledDataSource<bool>::New(desc.multiSampled))
.Build();
task_scene_index_->AddPrims({{buf_id,
pxr::HdPrimTypeTokens->renderBuffer,
pxr::HdRetainedContainerDataSource::New(
pxr::HdRenderBufferSchema::GetSchemaToken(), buffer_ds)}});
}
pxr::HdTaskSharedPtr RenderTaskDelegate::task()
{
return render_index_->GetTask(task_id_);
}
void RenderTaskDelegate::set_camera(pxr::SdfPath const &camera_id)
{
if (task_params_.camera == camera_id) {
return;
}
task_params_.camera = camera_id;
dirty_task_params();
}
bool RenderTaskDelegate::is_converged()
{
return static_cast<pxr::HdxRenderTask *>(task().get())->IsConverged();
}
void RenderTaskDelegate::set_viewport(pxr::GfVec4d const &viewport)
{
if (task_params_.viewport == viewport) {
return;
}
task_params_.viewport = viewport;
dirty_task_params();
int w = viewport[2] - viewport[0];
int h = viewport[3] - viewport[1];
for (auto &it : buffer_descriptors_) {
it.second.dimensions = pxr::GfVec3i(w, h, 1);
publish_buffer(it.first, it.second);
task_scene_index_->DirtyPrims({{it.first, pxr::HdRenderBufferSchema::GetDimensionsLocator()}});
}
}
void RenderTaskDelegate::add_aov(pxr::TfToken const &aov_key)
{
pxr::SdfPath buf_id = buffer_id(aov_key);
if (buffer_descriptors_.find(buf_id) != buffer_descriptors_.end()) {
return;
}
pxr::HdAovDescriptor aov_desc = render_index_->GetRenderDelegate()->GetDefaultAovDescriptor(
aov_key);
if (aov_desc.format == pxr::HdFormatInvalid) {
CLOG_ERROR(LOG_HYDRA_RENDER, "Invalid AOV: %s", aov_key.GetText());
return;
}
if (!ELEM(
pxr::HdGetComponentFormat(aov_desc.format), pxr::HdFormatFloat32, pxr::HdFormatFloat16))
{
CLOG_WARN(LOG_HYDRA_RENDER,
"Unsupported data format %s for AOV %s",
pxr::TfEnum::GetName(aov_desc.format).c_str(),
aov_key.GetText());
return;
}
int w = task_params_.viewport[2] - task_params_.viewport[0];
int h = task_params_.viewport[3] - task_params_.viewport[1];
pxr::HdRenderBufferDescriptor desc(
pxr::GfVec3i(w, h, 1), aov_desc.format, aov_desc.multiSampled);
buffer_descriptors_[buf_id] = desc;
publish_buffer(buf_id, desc);
pxr::HdRenderPassAovBinding binding;
binding.aovName = aov_key;
binding.renderBufferId = buf_id;
binding.aovSettings = aov_desc.aovSettings;
binding.clearValue = aov_desc.clearValue;
task_params_.aovBindings.push_back(binding);
dirty_task_params();
CLOG_DEBUG(LOG_HYDRA_RENDER, "%s", aov_key.GetText());
}
void RenderTaskDelegate::read_aov(pxr::TfToken const &aov_key, void *data)
{
pxr::HdRenderBuffer *buffer = static_cast<pxr::HdRenderBuffer *>(
render_index_->GetBprim(pxr::HdPrimTypeTokens->renderBuffer, buffer_id(aov_key)));
if (!buffer) {
return;
}
pxr::HdFormat format = buffer->GetFormat();
size_t len = buffer->GetWidth() * buffer->GetHeight() * pxr::HdGetComponentCount(format);
if (pxr::HdGetComponentFormat(format) == pxr::HdFormatFloat32) {
void *buf_data = buffer->Map();
memcpy(data, buf_data, len * sizeof(float));
buffer->Unmap();
}
else if (pxr::HdGetComponentFormat(format) == pxr::HdFormatFloat16) {
Eigen::half *buf_data = (Eigen::half *)buffer->Map();
float *fdata = static_cast<float *>(data);
for (size_t i = 0; i < len; ++i) {
fdata[i] = buf_data[i];
}
buffer->Unmap();
}
else {
BLI_assert_unreachable();
}
}
pxr::HdRenderBuffer *RenderTaskDelegate::get_aov_buffer(pxr::TfToken const &aov_key)
{
return (pxr::HdRenderBuffer *)render_index_->GetBprim(pxr::HdPrimTypeTokens->renderBuffer,
buffer_id(aov_key));
}
void RenderTaskDelegate::bind() {}
void RenderTaskDelegate::unbind() {}
pxr::SdfPath RenderTaskDelegate::buffer_id(pxr::TfToken const &aov_key) const
{
return base_id_.AppendElementString("aov_" + aov_key.GetString());
}
GPURenderTaskDelegate::~GPURenderTaskDelegate()
{
unbind();
if (tex_color_) {
GPU_texture_free(tex_color_);
}
if (tex_depth_) {
GPU_texture_free(tex_depth_);
}
}
void GPURenderTaskDelegate::set_viewport(pxr::GfVec4d const &viewport)
{
if (task_params_.viewport == viewport) {
return;
}
task_params_.viewport = viewport;
dirty_task_params();
if (tex_color_) {
GPU_texture_free(tex_color_);
tex_color_ = nullptr;
add_aov(pxr::HdAovTokens->color);
}
if (tex_depth_) {
GPU_texture_free(tex_depth_);
tex_depth_ = nullptr;
add_aov(pxr::HdAovTokens->depth);
}
}
void GPURenderTaskDelegate::add_aov(pxr::TfToken const &aov_key)
{
gpu::TextureFormat format;
gpu::Texture **tex;
if (aov_key == pxr::HdAovTokens->color) {
format = gpu::TextureFormat::SFLOAT_32_32_32_32;
tex = &tex_color_;
}
else if (aov_key == pxr::HdAovTokens->depth) {
format = gpu::TextureFormat::SFLOAT_32_DEPTH;
tex = &tex_depth_;
}
else {
CLOG_ERROR(LOG_HYDRA_RENDER, "Invalid AOV: %s", aov_key.GetText());
return;
}
if (*tex) {
return;
}
*tex = GPU_texture_create_2d(("tex_render_hydra_" + aov_key.GetString()).c_str(),
task_params_.viewport[2] - task_params_.viewport[0],
task_params_.viewport[3] - task_params_.viewport[1],
1,
format,
GPU_TEXTURE_USAGE_GENERAL,
nullptr);
CLOG_DEBUG(LOG_HYDRA_RENDER, "%s", aov_key.GetText());
}
void GPURenderTaskDelegate::read_aov(pxr::TfToken const &aov_key, void *data)
{
gpu::Texture *tex = nullptr;
int c;
if (aov_key == pxr::HdAovTokens->color) {
tex = tex_color_;
c = 4;
}
else if (aov_key == pxr::HdAovTokens->depth) {
tex = tex_depth_;
c = 1;
}
if (!tex) {
return;
}
int w = GPU_texture_width(tex), h = GPU_texture_height(tex);
void *tex_data = GPU_texture_read(tex, GPU_DATA_FLOAT, 0);
memcpy(data, tex_data, sizeof(float) * w * h * c);
MEM_delete_void(tex_data);
}
void GPURenderTaskDelegate::bind()
{
if (!framebuffer_) {
framebuffer_ = GPU_framebuffer_create("fb_render_hydra");
}
GPU_framebuffer_ensure_config(
&framebuffer_, {GPU_ATTACHMENT_TEXTURE(tex_depth_), GPU_ATTACHMENT_TEXTURE(tex_color_)});
GPU_framebuffer_bind(framebuffer_);
GPU_framebuffer_clear_color_depth(framebuffer_, {0.0, 0.0, 0.0, 0.0}, 1.0f);
#ifdef WITH_OPENGL_BACKEND
/* Workaround missing/buggy VAOs in hgiGL and hdSt. For OpenGL compatibility
* profile this is not a problem, but for core profile it is. */
if (VAO_ == 0 && GPU_backend_get_type() == GPU_BACKEND_OPENGL) {
glGenVertexArrays(1, &VAO_);
glBindVertexArray(VAO_);
}
#else
UNUSED_VARS(VAO_);
#endif
CLOG_DEBUG(LOG_HYDRA_RENDER, "bind");
}
void GPURenderTaskDelegate::unbind()
{
#ifdef WITH_OPENGL_BACKEND
if (VAO_) {
glDeleteVertexArrays(1, &VAO_);
VAO_ = 0;
}
#endif
if (framebuffer_) {
GPU_framebuffer_free(framebuffer_);
framebuffer_ = nullptr;
}
CLOG_DEBUG(LOG_HYDRA_RENDER, "unbind");
}
gpu::Texture *GPURenderTaskDelegate::get_aov_texture(pxr::TfToken const &aov_key)
{
if (aov_key == pxr::HdAovTokens->color) {
return tex_color_;
}
if (aov_key == pxr::HdAovTokens->depth) {
return tex_depth_;
}
return nullptr;
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <pxr/imaging/hd/dataSource.h>
#include <pxr/imaging/hd/renderIndex.h>
#include <pxr/imaging/hd/retainedSceneIndex.h>
#include <pxr/imaging/hd/task.h>
#include <pxr/imaging/hdx/renderSetupTask.h>
#include "GPU_framebuffer.hh"
#include "GPU_texture.hh"
namespace blender::render::hydra {
/* Registers a HdxRenderTask with the render index as a task prim. */
class RenderTaskParamsDataSource final
: public pxr::HdTypedSampledDataSource<pxr::HdxRenderTaskParams> {
public:
HD_DECLARE_DATASOURCE(RenderTaskParamsDataSource);
pxr::HdxRenderTaskParams params;
pxr::VtValue GetValue(Time /*shutterOffset*/) override
{
return pxr::VtValue(params);
}
pxr::HdxRenderTaskParams GetTypedValue(Time /*shutterOffset*/) override
{
return params;
}
bool GetContributingSampleTimesForInterval(Time /*start*/,
Time /*end*/,
std::vector<Time> * /*sampleTimes*/) override
{
return false;
}
};
class RenderTaskDelegate {
protected:
pxr::HdRenderIndex *render_index_ = nullptr;
pxr::HdRetainedSceneIndexRefPtr task_scene_index_;
pxr::SdfPath base_id_;
pxr::SdfPath task_id_;
RenderTaskParamsDataSource::Handle task_params_ds_ = RenderTaskParamsDataSource::New();
pxr::HdxRenderTaskParams &task_params_ = task_params_ds_->params;
pxr::TfHashMap<pxr::SdfPath, pxr::HdRenderBufferDescriptor, pxr::SdfPath::Hash>
buffer_descriptors_;
public:
RenderTaskDelegate(pxr::HdRenderIndex *render_index,
pxr::HdRetainedSceneIndexRefPtr task_scene_index,
pxr::SdfPath const &base_id);
virtual ~RenderTaskDelegate() = default;
pxr::HdTaskSharedPtr task();
void set_camera(pxr::SdfPath const &camera_id);
bool is_converged();
virtual void set_viewport(pxr::GfVec4d const &viewport);
virtual void add_aov(pxr::TfToken const &aov_key);
virtual void read_aov(pxr::TfToken const &aov_key, void *data);
pxr::HdRenderBuffer *get_aov_buffer(pxr::TfToken const &aov_key);
virtual void bind();
virtual void unbind();
protected:
pxr::SdfPath buffer_id(pxr::TfToken const &aov_key) const;
void publish_task();
void dirty_task_params();
void publish_buffer(pxr::SdfPath const &buf_id, pxr::HdRenderBufferDescriptor const &desc);
};
class GPURenderTaskDelegate : public RenderTaskDelegate {
private:
gpu::FrameBuffer *framebuffer_ = nullptr;
gpu::Texture *tex_color_ = nullptr;
gpu::Texture *tex_depth_ = nullptr;
unsigned int VAO_ = 0;
public:
using RenderTaskDelegate::RenderTaskDelegate;
~GPURenderTaskDelegate() override;
void set_viewport(pxr::GfVec4d const &viewport) override;
void add_aov(pxr::TfToken const &aov_key) override;
void read_aov(pxr::TfToken const &aov_key, void *data) override;
void bind() override;
void unbind() override;
gpu::Texture *get_aov_texture(pxr::TfToken const &aov_key);
};
} // namespace blender::render::hydra

View File

@@ -0,0 +1,306 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "viewport_engine.hh"
#include "camera.hh"
#include <pxr/base/gf/camera.h>
#include <pxr/imaging/glf/drawTarget.h>
#include <pxr/usd/usdGeom/camera.h>
#include "DNA_camera_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_vec_types.h" /* This include must be before `BKE_camera.h` due to `rctf` type. */
#include "DNA_view3d_types.h"
#include "BLI_math_matrix.h"
#include "BLI_time.h"
#include "BLI_timecode.h"
#include "BKE_camera.h"
#include "BKE_context.hh"
#include "GPU_matrix.hh"
#include "GPU_texture.hh"
#include "DEG_depsgraph_query.hh"
#include "RE_engine.h"
namespace blender::render::hydra {
struct ViewSettings {
int screen_width;
int screen_height;
pxr::GfVec4i border;
pxr::GfCamera camera;
ViewSettings(bContext *context);
int width();
int height();
};
ViewSettings::ViewSettings(bContext *context)
{
View3D *view3d = CTX_wm_view3d(context);
RegionView3D *region_data = static_cast<RegionView3D *>(CTX_wm_region_data(context));
ARegion *region = CTX_wm_region(context);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(context);
Scene *scene = DEG_get_evaluated_scene(depsgraph);
screen_width = region->winx;
screen_height = region->winy;
/* Getting render border. */
int x1 = 0, y1 = 0;
int x2 = screen_width, y2 = screen_height;
if (region_data->persp == RV3D_CAMOB) {
Object *camera_obj = scene->camera;
if ((scene->r.mode & R_BORDER) && camera_obj && camera_obj->type == OB_CAMERA) {
float camera_points[4][3];
BKE_camera_view_frame(scene, id_cast<Camera *>(camera_obj->data), camera_points);
float screen_points[4][2];
for (int i = 0; i < 4; i++) {
float world_location[] = {
camera_points[i][0], camera_points[i][1], camera_points[i][2], 1.0f};
mul_m4_v4(camera_obj->object_to_world().ptr(), world_location);
mul_m4_v4(region_data->persmat, world_location);
if (world_location[3] > 0.0) {
screen_points[i][0] = screen_width * 0.5f +
screen_width * 0.5f * (world_location[0] / world_location[3]);
screen_points[i][1] = screen_height * 0.5f +
screen_height * 0.5f * (world_location[1] / world_location[3]);
}
}
/* Getting camera view region. */
float x1_f = std::min(
{screen_points[0][0], screen_points[1][0], screen_points[2][0], screen_points[3][0]});
float x2_f = std::max(
{screen_points[0][0], screen_points[1][0], screen_points[2][0], screen_points[3][0]});
float y1_f = std::min(
{screen_points[0][1], screen_points[1][1], screen_points[2][1], screen_points[3][1]});
float y2_f = std::max(
{screen_points[0][1], screen_points[1][1], screen_points[2][1], screen_points[3][1]});
/* Adjusting region to border. */
float x = x1_f, y = y1_f;
float dx = x2_f - x1_f, dy = y2_f - y1_f;
x1 = x + scene->r.border.xmin * dx;
x2 = x + scene->r.border.xmax * dx;
y1 = y + scene->r.border.ymin * dy;
y2 = y + scene->r.border.ymax * dy;
/* Adjusting to region screen resolution. */
x1 = std::max(std::min(x1, screen_width), 0);
x2 = std::max(std::min(x2, screen_width), 0);
y1 = std::max(std::min(y1, screen_height), 0);
y2 = std::max(std::min(y2, screen_height), 0);
}
}
else {
if (view3d->flag2 & V3D_RENDER_BORDER) {
x1 = view3d->render_border.xmin * screen_width;
x2 = view3d->render_border.xmax * screen_width;
y1 = view3d->render_border.ymin * screen_height;
y2 = view3d->render_border.ymax * screen_height;
}
}
border = pxr::GfVec4i(x1, y1, x2, y2);
camera = gf_camera(depsgraph,
view3d,
region,
pxr::GfVec4f(float(border[0]) / screen_width,
float(border[1]) / screen_height,
float(width()) / screen_width,
float(height()) / screen_height));
}
int ViewSettings::width()
{
return border[2] - border[0];
}
int ViewSettings::height()
{
return border[3] - border[1];
}
DrawTexture::DrawTexture()
{
float coords[8] = {0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0};
GPUVertFormat format = {0};
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32);
GPU_vertformat_attr_add(&format, "texCoord", gpu::VertAttrType::SFLOAT_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, 4);
GPU_vertbuf_attr_fill(vbo, 0, coords);
GPU_vertbuf_attr_fill(vbo, 1, coords);
batch_ = GPU_batch_create_ex(GPU_PRIM_TRI_FAN, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
DrawTexture::~DrawTexture()
{
if (texture_) {
GPU_texture_free(texture_);
}
GPU_batch_discard(batch_);
}
void DrawTexture::create_from_buffer(pxr::HdRenderBuffer *buffer)
{
if (buffer == nullptr) {
return;
}
gpu::TextureFormat texture_format;
eGPUDataFormat data_format;
if (buffer->GetFormat() == pxr::HdFormat::HdFormatFloat16Vec4) {
texture_format = gpu::TextureFormat::SFLOAT_16_16_16_16;
data_format = GPU_DATA_HALF_FLOAT;
}
else {
texture_format = gpu::TextureFormat::SFLOAT_32_32_32_32;
data_format = GPU_DATA_FLOAT;
}
if (texture_ && (GPU_texture_width(texture_) != buffer->GetWidth() ||
GPU_texture_height(texture_) != buffer->GetHeight() ||
GPU_texture_format(texture_) != texture_format))
{
GPU_texture_free(texture_);
texture_ = nullptr;
}
if (texture_ == nullptr) {
texture_ = GPU_texture_create_2d("tex_hydra_render_viewport",
buffer->GetWidth(),
buffer->GetHeight(),
1,
texture_format,
GPU_TEXTURE_USAGE_GENERAL,
nullptr);
}
void *data = buffer->Map();
GPU_texture_update(texture_, data_format, data);
buffer->Unmap();
}
void DrawTexture::draw(gpu::Shader *shader, const pxr::GfVec4d &viewport, gpu::Texture *tex)
{
if (!tex) {
tex = texture_;
}
int slot = GPU_shader_get_sampler_binding(shader, "image");
GPU_texture_bind(tex, slot);
GPU_shader_uniform_1i(shader, "image", slot);
GPU_matrix_push();
GPU_matrix_translate_2f(viewport[0], viewport[1]);
GPU_matrix_scale_2f(viewport[2] - viewport[0], viewport[3] - viewport[1]);
GPU_batch_set_shader(batch_, shader);
GPU_batch_draw(batch_);
GPU_matrix_pop();
}
gpu::Texture *DrawTexture::texture() const
{
return texture_;
}
void ViewportEngine::render()
{
ViewSettings view_settings(context_);
if (view_settings.width() * view_settings.height() == 0) {
return;
};
free_camera_delegate_->SetCamera(view_settings.camera);
pxr::GfVec4d viewport(0.0, 0.0, view_settings.width(), view_settings.height());
render_task_delegate_->set_viewport(viewport);
if (light_tasks_delegate_) {
light_tasks_delegate_->set_viewport(viewport);
}
render_task_delegate_->add_aov(pxr::HdAovTokens->color);
render_task_delegate_->add_aov(pxr::HdAovTokens->depth);
gpu::FrameBuffer *view_framebuffer = GPU_framebuffer_active_get();
render_task_delegate_->bind();
auto t = tasks();
engine_->Execute(render_index_.get(), &t);
render_task_delegate_->unbind();
GPU_framebuffer_bind(view_framebuffer);
gpu::Shader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE);
GPU_shader_bind(shader);
pxr::GfVec4d draw_viewport(view_settings.border[0],
view_settings.border[1],
view_settings.border[2],
view_settings.border[3]);
GPURenderTaskDelegate *gpu_task = dynamic_cast<GPURenderTaskDelegate *>(
render_task_delegate_.get());
if (gpu_task) {
draw_texture_.draw(shader, draw_viewport, gpu_task->get_aov_texture(pxr::HdAovTokens->color));
}
else {
draw_texture_.create_from_buffer(
render_task_delegate_->get_aov_buffer(pxr::HdAovTokens->color));
draw_texture_.draw(shader, draw_viewport);
}
GPU_shader_unbind();
if (renderer_percent_done() == 0.0f) {
time_begin_ = BLI_time_now_seconds();
}
char elapsed_time[32];
BLI_timecode_string_from_time_simple(
elapsed_time, sizeof(elapsed_time), BLI_time_now_seconds() - time_begin_);
float percent_done = renderer_percent_done();
if (!render_task_delegate_->is_converged()) {
notify_status(percent_done / 100.0,
std ::string("Time: ") + elapsed_time +
" | Done: " + std::to_string(int(percent_done)) + "%",
"Render");
bl_engine_->flag |= RE_ENGINE_DO_DRAW;
}
else {
notify_status(percent_done / 100.0, std::string("Time: ") + elapsed_time, "Rendering Done");
}
}
void ViewportEngine::render(bContext *context)
{
context_ = context;
render();
}
void ViewportEngine::notify_status(float /*progress*/,
const std::string &info,
const std::string &status)
{
RE_engine_update_stats(bl_engine_, status.c_str(), info.c_str());
}
} // namespace blender::render::hydra

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <pxr/imaging/hd/renderBuffer.h>
#include "GPU_batch.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "engine.hh"
namespace blender::render::hydra {
class DrawTexture {
private:
gpu::Texture *texture_ = nullptr;
gpu::Batch *batch_;
public:
DrawTexture();
~DrawTexture();
void create_from_buffer(pxr::HdRenderBuffer *buffer);
void draw(gpu::Shader *shader, const pxr::GfVec4d &viewport, gpu::Texture *tex = nullptr);
gpu::Texture *texture() const;
private:
};
class ViewportEngine : public Engine {
private:
double time_begin_;
DrawTexture draw_texture_;
public:
using Engine::Engine;
void render() override;
void render(bContext *context);
protected:
void notify_status(float progress, const std::string &info, const std::string &status) override;
};
} // namespace blender::render::hydra

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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(&params);
BKE_camera_params_from_object(&params, cam_ob);
BKE_camera_multiview_params(&re->r, &params, cam_ob, re->viewname);
/* Compute matrix, view-plane, etc. */
BKE_camera_params_compute_viewplane(&params, re->winx, re->winy, re->r.xasp, re->r.yasp);
BKE_camera_params_compute_matrix(&params);
/* 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(&params.viewplane), BLI_rctf_size_y(&params.viewplane));
params.viewplane.xmin -= overscan;
params.viewplane.xmax += overscan;
params.viewplane.ymin -= overscan;
params.viewplane.ymax += overscan;
BKE_camera_params_compute_matrix(&params);
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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