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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,458 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
/* This is the Render Functions used by Realtime engines to draw with OpenGL */
#pragma once
#include <functional>
#include "BKE_mesh_wrapper.hh"
#include "BKE_subdiv_modifier.hh"
#include "BLI_math_vector_types.hh"
#include "DNA_object_enums.h"
#include "DNA_object_types.h"
#include "GPU_material.hh"
namespace blender {
namespace gpu {
class Batch;
class Shader;
class Texture;
class UniformBuf;
class FrameBuffer;
} // namespace gpu
struct ARegion;
struct bContext;
struct DefaultFramebufferList;
struct DefaultTextureList;
struct Depsgraph;
struct DupliObject;
struct GPUMaterial;
struct Mesh;
struct Object;
struct ParticleSystem;
struct rcti;
struct RegionView3D;
struct RenderEngine;
struct RenderEngineType;
struct RenderLayer;
struct RenderResult;
struct SpaceLink;
struct TaskGraph;
struct View3D;
struct ViewLayer;
struct DRWContext;
struct World;
struct DRWData;
struct DRWViewData;
struct GPUViewport;
struct DRWTextStore;
struct GPUViewport;
namespace draw {
class TextureFromPool;
class ObjectRef;
class Manager;
} // namespace draw
/* TODO: Put it somewhere else? */
struct BoundSphere {
float center[3], radius;
};
struct DrawEngine {
static constexpr int GPU_INFO_SIZE = 512; /* IMA_MAX_RENDER_TEXT_SIZE */
char info[GPU_INFO_SIZE] = {'\0'};
bool used = false;
virtual ~DrawEngine() = default;
virtual StringRefNull name_get() = 0;
/* Functions called for viewport. */
/** Init engine. Run first and for every redraw. */
virtual void init() = 0;
/** Scene synchronization. Command buffers building. */
virtual void begin_sync() = 0;
virtual void object_sync(draw::ObjectRef &ob_ref, draw::Manager &manager) = 0;
virtual void end_sync() = 0;
/** Command Submission. */
virtual void draw(draw::Manager &manager) = 0;
/* Called when closing blender.
* Cleanup all lazily initialized static members that have GPU resources.
* Implemented on a case by case basis and called directly. */
// static void exit(){};
struct Pointer {
DrawEngine *instance = nullptr;
~Pointer()
{
free_instance();
}
void free_instance()
{
delete instance;
instance = nullptr;
}
void set_used(bool used)
{
if (used) {
if (instance == nullptr) {
instance = create_instance();
}
instance->used = true;
}
else if (instance) {
instance->used = false;
}
}
virtual DrawEngine *create_instance() = 0;
};
};
/* Viewport. */
/**
* Returns a TextureFromPool stored in the given view data for the pass identified by the given
* pass name. Engines should call this function for each of the passes needed by the viewport
* compositor in every redraw, then it should allocate the texture and write the pass data to it.
* The texture should cover the entire viewport.
*/
draw::TextureFromPool &DRW_viewport_pass_texture_get(const char *pass_name);
/**
* Returns true if the pass with the given name exists.
*/
bool DRW_viewport_pass_texture_exists(const char *pass_name);
void DRW_viewport_request_redraw();
void DRW_render_to_image(
RenderEngine *engine,
Depsgraph *depsgraph,
::std::function<void(RenderEngine *, RenderLayer *, const rcti)> render_view_cb,
::std::function<void(RenderResult *)> store_metadata_cb);
void DRW_render_object_iter(RenderEngine *engine,
Depsgraph *depsgraph,
::std::function<void(draw::ObjectRef &, RenderEngine *, Depsgraph *)>);
void DRW_render_set_time(RenderEngine *engine, Depsgraph *depsgraph, int frame, float subframe);
/**
* Assume a valid GL context is bound (and that the gl_context_mutex has been acquired).
* This function only setup DST and execute the given function.
* \warning similar to DRW_render_to_image you cannot use default lists (`dfbl` & `dtxl`).
*/
void DRW_custom_pipeline_begin(DRWContext &draw_ctx, Depsgraph *depsgraph);
void DRW_custom_pipeline_end(DRWContext &draw_ctx);
/**
* Used when the render engine want to redo another cache populate inside the same render frame.
* Assumes it is called between `DRW_custom_pipeline_begin/end()`.
*/
void DRW_cache_restart();
/* Settings. */
bool DRW_object_is_renderable(const Object *ob);
/**
* Does `ob` needs to be rendered in edit mode.
*
* When using duplicate linked meshes, objects that are not in edit-mode will be drawn as
* it is in edit mode, when another object with the same mesh is in edit mode.
* This will not be the case when one of the objects are influenced by modifiers.
*/
bool DRW_object_is_in_edit_mode(const Object *ob);
/**
* Return whether this object is visible depending if
* we are rendering or drawing in the viewport.
*/
int DRW_object_visibility_in_active_context(const Object *ob);
bool DRW_object_use_hide_faces(const Object *ob);
bool DRW_object_is_visible_psys_in_active_context(const Object *object,
const ParticleSystem *psys);
/**
* Convenient accessor for object data, that also automatically returns
* the base or tessellated mesh depending if GPU subdivision is enabled.
*/
template<typename T> T &DRW_object_get_data_for_drawing(const Object &object)
{
return *id_cast<T *>(object.data);
}
inline Mesh &DRW_mesh_get_for_drawing(Mesh &mesh)
{
/* For drawing we want either the base mesh if GPU subdivision is enabled, or the
* tessellated mesh if GPU subdivision is disabled. */
if (BKE_subsurf_modifier_has_gpu_subdiv(&mesh)) {
return mesh;
}
return *BKE_mesh_wrapper_ensure_subdivision(&mesh);
}
template<> inline Mesh &DRW_object_get_data_for_drawing(const Object &object)
{
BLI_assert(object.type == OB_MESH);
return DRW_mesh_get_for_drawing(*id_cast<Mesh *>(object.data));
}
/**
* Same as DRW_object_get_data_for_drawing, but for the editmesh cage,
* if it exists.
*/
const Mesh *DRW_object_get_editmesh_cage_for_drawing(const Object &object);
/* Draw State. */
/* -------------------------------------------------------------------- */
/** \name Draw Context
* \{ */
struct DRWContext {
private:
/** Render State: No persistent data between draw calls. */
static thread_local DRWContext *g_context;
/** Timings recorded for performance overlay. */
float last_sync_time_;
float last_submission_time_;
/* TODO(fclem): Private? */
public:
/* TODO: clean up this struct a bit. */
/** Cache generation */
DRWData *data = nullptr;
/** Active view data structure for one of the 2 stereo view. */
DRWViewData *view_data_active = nullptr;
/** Optional associated viewport. Can be nullptr. */
GPUViewport *viewport = nullptr;
/** Size of the viewport or the final render frame. */
float2 size = {0, 0};
float2 inv_size = {0, 0};
/** Returns the viewport's default frame-buffer. */
gpu::FrameBuffer *default_framebuffer();
/** Returns the viewport's default frame-buffer list. Not all of them might be available. */
DefaultFramebufferList *viewport_framebuffer_list_get() const;
/** Returns the viewport's default texture list. Not all of them might be available. */
DefaultTextureList *viewport_texture_list_get() const;
const enum Mode {
/** Render for display of 2D or 3D area. Runs on main thread. */
VIEWPORT = 0,
/* These viewport modes will render without some overlays (i.e. no text). */
/** Render for a 3D viewport in XR. Runs on main thread. */
VIEWPORT_XR,
/** Render for a 3D viewport offscreen render (python). Runs on main thread. */
VIEWPORT_OFFSCREEN,
/**
* Render for a 3D viewport image render (Render Viewport Preview, also VSE scene strips).
* Runs on main thread.
*/
VIEWPORT_RENDER,
/** Render for object mode selection. Runs on main thread. */
SELECT_OBJECT,
/** Render for object material selection. Runs on main thread. */
SELECT_OBJECT_MATERIAL,
/** Render for edit mesh selection. Runs on main thread. */
SELECT_EDIT_MESH,
/** Render for depth picking (auto-depth). Runs on main thread. */
DEPTH,
DEPTH_ACTIVE_OBJECT,
/** Render for F12 final render. Can run in any thread. */
RENDER,
/** Used by custom pipeline. Can run in any thread. */
CUSTOM,
} mode;
struct {
bool draw_background = false;
} options;
/** Convenience pointer to text_store owned by the viewport */
DRWTextStore **text_store_p = nullptr;
/** Contains list of objects that needs to be extracted from other objects. */
Set<Object *> delayed_extraction;
/* TODO(fclem): Public. */
/** Current rendering context. Avoid too many lookups while drawing. */
/** Evaluated Depsgraph. */
Depsgraph *depsgraph = nullptr;
/** Evaluated Scene. */
Scene *scene = nullptr;
/** Evaluated ViewLayer. */
ViewLayer *view_layer = nullptr;
/** Last resort (some functions take this as an arg so we can't easily avoid).
* May be nullptr when used for selection or depth buffer. */
const bContext *evil_C = nullptr;
/** Can be nullptr depending on context. */
ARegion *region = nullptr;
/** Can be nullptr depending on context. */
SpaceLink *space_data = nullptr;
/** Can be nullptr depending on context. */
RegionView3D *rv3d = nullptr;
/** Can be nullptr depending on context. */
View3D *v3d = nullptr;
/** Use 'object_edit' for edit-mode */
Object *obact = nullptr;
Object *object_pose = nullptr;
Object *object_edit = nullptr;
eObjectMode object_mode = OB_MODE_OBJECT;
public:
/**
* If `viewport` is not specified, `DRWData` will be considered temporary and discarded on exit.
* If `C` is nullptr, it means that the context is **not** associated with any UI or operator.
* If `region` is nullptr, it will be sourced from the context `C` or left as nullptr otherwise.
* If `v3d` is nullptr, it will be sourced from the context `C` or left as nullptr otherwise.
*/
DRWContext(Mode mode,
Depsgraph *depsgraph,
GPUViewport *viewport,
const bContext *C = nullptr,
ARegion *region = nullptr,
View3D *v3d = nullptr);
DRWContext(Mode mode,
Depsgraph *depsgraph,
const int2 size = {1, 1},
const bContext *C = nullptr,
ARegion *region = nullptr,
View3D *v3d = nullptr);
~DRWContext();
/**
* Acquire `data` and `view_data_active`.
* Needs to be called before enabling any draw engine.
* IMPORTANT: This can be called multiple times before release_data.
* IMPORTANT: This must be called with an active GPUContext.
*/
void acquire_data();
/**
* Make sure to release acquired DRWData. If created on the fly, make sure to destroy them.
* IMPORTANT: This needs to be called with the same active GPUContext `acquire_data()` was called
* with.
*/
void release_data();
/**
* Enable engines from context. Not needed for Mode::RENDER and Mode::CUSTOM.
*
* `render_engine_type` specify the engine to use in OB_MATERIAL or OB_RENDER modes.
* `gpencil_engine_needed` should be set to true if the grease pencil engine is needed.
*/
void enable_engines(bool gpencil_engine_needed = false,
RenderEngineType *render_engine_type = nullptr);
/** Free unused engine data. */
void engines_data_validate();
using iter_callback_t =
::std::function<void(struct DupliCacheManager &, struct ExtractionGraph &)>;
/** Run the sync phase with data extraction. iter_callback defines which object to sync. */
void sync(iter_callback_t iter_callback);
/** Run enabled engine init and sync callbacks. iter_callback defines which object to sync. */
void engines_init_and_sync(iter_callback_t iter_callback);
/** Run enabled engine init and draw scene callbacks. */
void engines_draw_scene();
static DRWContext &get_active()
{
return *g_context;
}
float2 viewport_size_get() const
{
return size;
}
/** Return true if any #DRWContext is active on this thread. */
static bool is_active()
{
return g_context != nullptr;
}
bool is_select() const
{
return ELEM(mode, SELECT_OBJECT, SELECT_OBJECT_MATERIAL, SELECT_EDIT_MESH);
}
bool is_material_select() const
{
return ELEM(mode, SELECT_OBJECT_MATERIAL);
}
bool is_depth() const
{
return ELEM(mode, DEPTH, DEPTH_ACTIVE_OBJECT);
}
bool is_image_render() const
{
return ELEM(mode, VIEWPORT_RENDER, RENDER);
}
bool is_scene_render() const
{
return ELEM(mode, RENDER);
}
bool is_viewport_image_render() const
{
return ELEM(mode, VIEWPORT_RENDER);
}
bool is_viewport_xr() const
{
return ELEM(mode, VIEWPORT_XR);
}
float last_sync_time() const
{
return last_sync_time_;
}
float last_submission_time() const
{
return last_submission_time_;
}
/** True if current viewport is drawn during playback. */
bool is_playback() const;
/** True if current viewport is drawn during navigation operator. */
bool is_navigating() const;
/** True if current viewport is drawn during painting operator. */
bool is_painting() const;
/** True if current viewport is drawn during transforming operator. */
bool is_transforming() const;
/** True if viewport compositor is enabled when drawing with this context. */
bool is_viewport_compositor_enabled() const;
};
/** \} */
const DRWContext *DRW_context_get();
} // namespace blender

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_array_utils.hh"
#include "BKE_attribute_math.hh"
#include "GPU_vertex_buffer.hh"
#include "attribute_convert.hh"
namespace blender::draw {
GPUVertFormat init_format_for_attribute(const bke::AttrType data_type, const StringRef vbo_name)
{
GPUVertFormat format{};
bke::attribute_math::to_static_type(data_type, [&]<typename T>() {
using Converter = AttributeConverter<T>;
if constexpr (!std::is_void_v<typename Converter::VBOType>) {
GPU_vertformat_attr_add_legacy(&format,
vbo_name,
Converter::gpu_component_type,
Converter::gpu_component_len,
Converter::gpu_fetch_mode);
}
});
return format;
}
void vertbuf_data_extract_direct(const GSpan attribute, gpu::VertBuf &vbo)
{
bke::attribute_math::to_static_type(attribute.type(), [&]<typename T>() {
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
if constexpr (!std::is_void_v<VBOType>) {
const Span<T> src = attribute.typed<T>();
MutableSpan<VBOType> data = vbo.data<VBOType>();
if constexpr (std::is_same_v<T, VBOType>) {
array_utils::copy(src, data);
}
else {
threading::parallel_for(src.index_range(), 8192, [&](const IndexRange range) {
for (const int i : range) {
data[i] = Converter::convert(src[i]);
}
});
}
}
});
}
} // namespace blender::draw

View File

@@ -0,0 +1,170 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "BLI_color_types.hh"
#include "BLI_generic_span.hh"
#include "BLI_math_color.h"
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "GPU_vertex_format.hh"
#include "IMB_colormanagement.hh"
namespace blender {
namespace gpu {
class VertBuf;
}
namespace bke {
enum class AttrType : int16_t;
}
/**
* Component length of 3 is used for scalars because implicit conversion is done by OpenGL from a
* scalar `s` will produce `float4(s, 0, 0, 1)`. However, following the Blender convention, it
* should be `float4(s, s, s, 1)`.
*/
constexpr int COMPONENT_LEN_SCALAR = 3;
namespace draw {
/**
* Utility to convert from the type used in the attributes to the types for GPU vertex buffers.
*/
template<typename T> struct AttributeConverter {
using VBOType = void;
};
template<> struct AttributeConverter<bool> {
using VBOType = VecBase<float, COMPONENT_LEN_SCALAR>;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = COMPONENT_LEN_SCALAR;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const bool &value)
{
return VBOType(value);
}
};
template<> struct AttributeConverter<int8_t> {
using VBOType = VecBase<float, COMPONENT_LEN_SCALAR>;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = COMPONENT_LEN_SCALAR;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const int8_t &value)
{
return VecBase<float, COMPONENT_LEN_SCALAR>(value);
}
};
template<> struct AttributeConverter<int> {
using VBOType = VecBase<float, COMPONENT_LEN_SCALAR>;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = COMPONENT_LEN_SCALAR;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const int &value)
{
return VecBase<float, COMPONENT_LEN_SCALAR>(value);
}
};
template<> struct AttributeConverter<int2> {
using VBOType = float2;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = 2;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const int2 &value)
{
return float2(value.x, value.y);
}
};
template<> struct AttributeConverter<float> {
using VBOType = VecBase<float, COMPONENT_LEN_SCALAR>;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = COMPONENT_LEN_SCALAR;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const float &value)
{
return VBOType(value);
}
};
template<> struct AttributeConverter<float2> {
using VBOType = float2;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = 2;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const float2 &value)
{
return value;
}
};
template<> struct AttributeConverter<float3> {
using VBOType = float3;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = 3;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const float3 &value)
{
return value;
}
};
template<> struct AttributeConverter<ColorGeometry4b> {
/* 16 bits are required to store the color in linear space without precision loss. */
using VBOType = ushort4;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_U16;
static constexpr int gpu_component_len = 4;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_INT_TO_FLOAT_UNIT;
static VBOType convert(const ColorGeometry4b &value)
{
float3 rgb = {BLI_color_from_srgb_table[value.r],
BLI_color_from_srgb_table[value.g],
BLI_color_from_srgb_table[value.b]};
IMB_colormanagement_rec709_to_scene_linear(rgb, rgb);
return {unit_float_to_ushort_clamp(rgb[0]),
unit_float_to_ushort_clamp(rgb[1]),
unit_float_to_ushort_clamp(rgb[2]),
ushort(value.a * 257)};
}
};
template<> struct AttributeConverter<ColorGeometry4f> {
using VBOType = ColorGeometry4f;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = 4;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const ColorGeometry4f &value)
{
return value;
}
};
template<> struct AttributeConverter<math::Quaternion> {
using VBOType = float4;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = 4;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const math::Quaternion &value)
{
return float4(value.w, value.x, value.y, value.z);
}
};
template<> struct AttributeConverter<float4> {
using VBOType = float4;
static constexpr GPUVertCompType gpu_component_type = GPU_COMP_F32;
static constexpr int gpu_component_len = 4;
static constexpr GPUVertFetchMode gpu_fetch_mode = GPU_FETCH_FLOAT;
static VBOType convert(const float4 &value)
{
return value;
}
};
GPUVertFormat init_format_for_attribute(bke::AttrType data_type, StringRef vbo_name);
void vertbuf_data_extract_direct(GSpan attribute, gpu::VertBuf &vbo);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#ifndef GPU_SHADER
# include "GPU_shader_shared_utils.hh"
#endif
/* Copy of DNA enum in `DNA_curves_types.h`. */
enum [[host_shared]] CurveType : uint32_t {
CURVE_TYPE_CATMULL_ROM = 0u,
CURVE_TYPE_POLY = 1u,
CURVE_TYPE_BEZIER = 2u,
CURVE_TYPE_NURBS = 3u,
};
/* -------------------------------------------------------------------- */
/** \name Generic Attribute
*
* These types are necessary to overcome the issue with float3 alignment on GPU.
* Having all types using the same interface allows the usage of templates to load and manipulate
* them inside the shaders.
* \{ */
struct [[host_shared]] StoredFloat4 {
float x;
float y;
float z;
float w;
};
struct [[host_shared]] StoredFloat3 {
float x;
float y;
float z;
};
struct [[host_shared]] StoredFloat2 {
float x;
float y;
};
struct [[host_shared]] StoredFloat {
float x;
};
float4 load_data(StoredFloat4 data)
{
return float4(data.x, data.y, data.z, data.w);
}
float3 load_data(StoredFloat3 data)
{
return float3(data.x, data.y, data.z);
}
float2 load_data(StoredFloat2 data)
{
return float2(data.x, data.y);
}
float load_data(StoredFloat data)
{
return float(data.x);
}
StoredFloat4 as_data(float4 interp)
{
StoredFloat4 data;
data.x = interp.x;
data.y = interp.y;
data.z = interp.z;
data.w = interp.w;
return data;
}
StoredFloat3 as_data(float3 interp)
{
StoredFloat3 data;
data.x = interp.x;
data.y = interp.y;
data.z = interp.z;
return data;
}
StoredFloat2 as_data(float2 interp)
{
StoredFloat2 data;
data.x = interp.x;
data.y = interp.y;
return data;
}
StoredFloat as_data(float interp)
{
StoredFloat data;
data.x = interp;
return data;
}
/** \} */

View File

@@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GPU_shader.hh"
#include "draw_attributes.hh"
namespace blender::draw {
void drw_attributes_merge(VectorSet<std::string> *dst, const VectorSet<std::string> *src)
{
dst->add_multiple(src->as_span());
}
bool drw_attributes_overlap(const VectorSet<std::string> *a, const VectorSet<std::string> *b)
{
for (const std::string &req : b->as_span()) {
if (!a->contains(req)) {
return false;
}
}
return true;
}
void drw_attributes_add_request(VectorSet<std::string> *attrs, const StringRef name)
{
if (attrs->size() >= GPU_MAX_ATTR) {
return;
}
attrs->add_as(name);
}
} // namespace blender::draw

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Utilities for rendering attributes.
*/
#pragma once
#include <string>
#include "BLI_string_ref.hh"
#include "BLI_sys_types.h"
#include "BLI_vector_set.hh"
namespace blender {
namespace bke {
enum class AttrDomain : int8_t;
}
namespace draw {
struct DRW_MeshCDMask {
VectorSet<std::string> uv;
VectorSet<std::string> tan;
bool orco = false;
bool tan_orco = false;
bool sculpt_overlays = false;
bool edit_uv = false;
friend bool operator==(const DRW_MeshCDMask &a, const DRW_MeshCDMask &b) = default;
void clear()
{
uv.clear_and_keep_capacity();
tan.clear_and_keep_capacity();
orco = false;
tan_orco = false;
sculpt_overlays = false;
edit_uv = false;
}
};
void drw_attributes_merge(VectorSet<std::string> *dst, const VectorSet<std::string> *src);
/* Return true if all requests in b are in a. */
bool drw_attributes_overlap(const VectorSet<std::string> *a, const VectorSet<std::string> *b);
void drw_attributes_add_request(VectorSet<std::string> *attrs, StringRef name);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,622 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "DNA_curve_types.h"
#include "DNA_curves_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_scene_types.h"
#include "DNA_volume_types.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_attribute.hh"
#include "BKE_context.hh"
#include "BKE_mesh_wrapper.hh"
#include "BKE_object.hh"
#include "BKE_object_types.hh"
#include "BKE_subdiv_modifier.hh"
#include "DRW_render.hh"
#include "GPU_batch.hh"
#include "GPU_batch_utils.hh"
#include "GPU_capabilities.hh"
#include "draw_cache.hh"
#include "draw_cache_impl.hh"
#include "draw_context_private.hh"
/* -------------------------------------------------------------------- */
/** \name Internal Defines
* \{ */
#define VCLASS_LIGHT_AREA_SHAPE (1 << 0)
#define VCLASS_LIGHT_SPOT_SHAPE (1 << 1)
#define VCLASS_LIGHT_SPOT_BLEND (1 << 2)
#define VCLASS_LIGHT_SPOT_CONE (1 << 3)
#define VCLASS_LIGHT_DIST (1 << 4)
#define VCLASS_CAMERA_FRAME (1 << 5)
#define VCLASS_CAMERA_DIST (1 << 6)
#define VCLASS_CAMERA_VOLUME (1 << 7)
#define VCLASS_SCREENSPACE (1 << 8)
#define VCLASS_SCREENALIGNED (1 << 9)
#define VCLASS_EMPTY_SCALED (1 << 10)
#define VCLASS_EMPTY_AXES (1 << 11)
#define VCLASS_EMPTY_AXES_NAME (1 << 12)
#define VCLASS_EMPTY_AXES_SHADOW (1 << 13)
#define VCLASS_EMPTY_SIZE (1 << 14)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common
* \{ */
namespace blender::draw {
void DRW_vertbuf_create_wiredata(gpu::VertBuf *vbo, const int vert_len)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute("wd",
gpu::VertAttrType::SFLOAT_32);
GPU_vertbuf_init_with_format(*vbo, format);
GPU_vertbuf_data_alloc(*vbo, vert_len);
vbo->data<float>().fill(1.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Common Object API
*
* \note Curve and text objects evaluate to the evaluated geometry set's mesh component if
* they have a surface, so curve objects themselves do not have a surface (the mesh component
* is presented to render engines as a separate object).
* \{ */
gpu::Batch *DRW_cache_object_all_edges_get(Object *ob)
{
switch (ob->type) {
case OB_MESH:
return DRW_cache_mesh_all_edges_get(ob);
/* TODO: should match #DRW_cache_object_surface_get. */
default:
return nullptr;
}
}
gpu::Batch *DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold)
{
switch (ob->type) {
case OB_MESH:
return DRW_cache_mesh_edge_detection_get(ob, r_is_manifold);
default:
return nullptr;
}
}
gpu::Batch *DRW_cache_object_face_wireframe_get(const Scene *scene, Object *ob)
{
switch (ob->type) {
case OB_MESH:
return DRW_cache_mesh_face_wireframe_get(ob);
case OB_POINTCLOUD:
return DRW_pointcloud_batch_cache_get_dots(ob);
case OB_VOLUME:
return DRW_cache_volume_face_wireframe_get(ob);
case OB_GREASE_PENCIL:
return DRW_cache_grease_pencil_face_wireframe_get(scene, ob);
default:
return nullptr;
}
}
gpu::Batch *DRW_cache_object_loose_edges_get(Object *ob)
{
switch (ob->type) {
case OB_MESH:
return DRW_cache_mesh_loose_edges_get(ob);
default:
return nullptr;
}
}
gpu::Batch *DRW_cache_object_surface_get(Object *ob)
{
switch (ob->type) {
case OB_MESH:
return DRW_cache_mesh_surface_get(ob);
default:
return nullptr;
}
}
Span<gpu::Batch *> DRW_cache_object_surface_material_get(Object *ob,
const Span<const GPUMaterial *> materials)
{
switch (ob->type) {
case OB_MESH:
return DRW_cache_mesh_surface_shaded_get(ob, materials);
default:
return {};
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Meshes
* \{ */
gpu::Batch *DRW_cache_mesh_all_verts_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_all_verts(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_paint_overlay_verts_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_paint_overlay_verts(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_all_edges_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_all_edges(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_loose_edges_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_loose_edges(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_edge_detection_get(Object *ob, bool *r_is_manifold)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_edge_detection(DRW_object_get_data_for_drawing<Mesh>(*ob),
r_is_manifold);
}
gpu::Batch *DRW_cache_mesh_surface_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_paint_overlay_surface_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_paint_overlay_surface(
DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_paint_overlay_edges_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_paint_overlay_edges(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
Span<gpu::Batch *> DRW_cache_mesh_surface_shaded_get(Object *ob,
const Span<const GPUMaterial *> materials)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_shaded(
*ob, DRW_object_get_data_for_drawing<Mesh>(*ob), materials);
}
Span<gpu::Batch *> DRW_cache_mesh_surface_texpaint_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_texpaint(*ob,
DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_surface_texpaint_single_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_texpaint_single(
*ob, DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_surface_vertpaint_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_vertpaint(*ob,
DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_surface_sculptcolors_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_sculpt(*ob, DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_surface_weights_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_weights(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_face_wireframe_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_wireframes_face(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_edit_mesh_analysis(DRW_object_get_data_for_drawing<Mesh>(*ob));
}
gpu::Batch *DRW_cache_mesh_surface_viewer_attribute_get(Object *ob)
{
BLI_assert(ob->type == OB_MESH);
return DRW_mesh_batch_cache_get_surface_viewer_attribute(
DRW_object_get_data_for_drawing<Mesh>(*ob));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Curve
* \{ */
gpu::Batch *DRW_cache_curve_edge_wire_get(Object *ob)
{
BLI_assert(ob->type == OB_CURVES_LEGACY);
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_wire_edge(&cu);
}
gpu::Batch *DRW_cache_curve_edge_wire_viewer_attribute_get(Object *ob)
{
BLI_assert(ob->type == OB_CURVES_LEGACY);
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_wire_edge_viewer_attribute(&cu);
}
gpu::Batch *DRW_cache_curve_edge_normal_get(Object *ob)
{
BLI_assert(ob->type == OB_CURVES_LEGACY);
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_normal_edge(&cu);
}
gpu::Batch *DRW_cache_curve_edge_overlay_get(Object *ob)
{
BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF));
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_edit_edges(&cu);
}
gpu::Batch *DRW_cache_curve_vert_overlay_get(Object *ob)
{
BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF));
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_edit_verts(&cu);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Font
* \{ */
gpu::Batch *DRW_cache_text_edge_wire_get(Object *ob)
{
BLI_assert(ob->type == OB_FONT);
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_wire_edge(&cu);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Surface
* \{ */
gpu::Batch *DRW_cache_surf_edge_wire_get(Object *ob)
{
BLI_assert(ob->type == OB_SURF);
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ob);
return DRW_curve_batch_cache_get_wire_edge(&cu);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lattice
* \{ */
gpu::Batch *DRW_cache_lattice_verts_get(Object *ob)
{
BLI_assert(ob->type == OB_LATTICE);
Lattice &lt = DRW_object_get_data_for_drawing<Lattice>(*ob);
return DRW_lattice_batch_cache_get_all_verts(&lt);
}
gpu::Batch *DRW_cache_lattice_wire_get(Object *ob, bool use_weight)
{
BLI_assert(ob->type == OB_LATTICE);
Lattice &lt = DRW_object_get_data_for_drawing<Lattice>(*ob);
int actdef = -1;
if (use_weight && !lt.vertex_group_names.is_empty() && lt.editlatt && lt.editlatt->latt->dvert) {
actdef = lt.vertex_group_active_index - 1;
}
return DRW_lattice_batch_cache_get_all_edges(&lt, use_weight, actdef);
}
gpu::Batch *DRW_cache_lattice_vert_overlay_get(Object *ob)
{
BLI_assert(ob->type == OB_LATTICE);
Lattice &lt = DRW_object_get_data_for_drawing<Lattice>(*ob);
return DRW_lattice_batch_cache_get_edit_verts(&lt);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name PointCloud
* \{ */
gpu::Batch *DRW_cache_pointcloud_vert_overlay_get(Object *ob)
{
BLI_assert(ob->type == OB_POINTCLOUD);
PointCloud &pointcloud = DRW_object_get_data_for_drawing<PointCloud>(*ob);
return DRW_pointcloud_batch_cache_get_edit_dots(&pointcloud);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume
* \{ */
gpu::Batch *DRW_cache_volume_face_wireframe_get(Object *ob)
{
BLI_assert(ob->type == OB_VOLUME);
return DRW_volume_batch_cache_get_wireframes_face(&DRW_object_get_data_for_drawing<Volume>(*ob));
}
gpu::Batch *DRW_cache_volume_selection_surface_get(Object *ob)
{
BLI_assert(ob->type == OB_VOLUME);
return DRW_volume_batch_cache_get_selection_surface(
&DRW_object_get_data_for_drawing<Volume>(*ob));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Particles
* \{ */
gpu::Batch *DRW_cache_particles_get_hair(Object *object, ParticleSystem *psys, ModifierData *md)
{
return DRW_particles_batch_cache_get_hair(object, psys, md);
}
gpu::Batch *DRW_cache_particles_get_dots(Object *object, ParticleSystem *psys)
{
return DRW_particles_batch_cache_get_dots(object, psys);
}
gpu::Batch *DRW_cache_particles_get_edit_strands(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit,
bool use_weight)
{
return DRW_particles_batch_cache_get_edit_strands(object, psys, edit, use_weight);
}
gpu::Batch *DRW_cache_particles_get_edit_inner_points(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit)
{
return DRW_particles_batch_cache_get_edit_inner_points(object, psys, edit);
}
gpu::Batch *DRW_cache_particles_get_edit_tip_points(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit)
{
return DRW_particles_batch_cache_get_edit_tip_points(object, psys, edit);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Batch Cache Implementation (common)
* \{ */
void drw_batch_cache_validate(Object *ob)
{
switch (ob->type) {
case OB_MESH:
DRW_mesh_batch_cache_validate(DRW_object_get_data_for_drawing<Mesh>(*ob));
break;
case OB_CURVES_LEGACY:
case OB_FONT:
case OB_SURF:
DRW_curve_batch_cache_validate(&DRW_object_get_data_for_drawing<Curve>(*ob));
break;
case OB_LATTICE:
DRW_lattice_batch_cache_validate(&DRW_object_get_data_for_drawing<Lattice>(*ob));
break;
case OB_CURVES:
DRW_curves_batch_cache_validate(&DRW_object_get_data_for_drawing<Curves>(*ob));
break;
case OB_POINTCLOUD:
DRW_pointcloud_batch_cache_validate(&DRW_object_get_data_for_drawing<PointCloud>(*ob));
break;
case OB_VOLUME:
DRW_volume_batch_cache_validate(&DRW_object_get_data_for_drawing<Volume>(*ob));
break;
case OB_GREASE_PENCIL:
DRW_grease_pencil_batch_cache_validate(&DRW_object_get_data_for_drawing<GreasePencil>(*ob));
default:
break;
}
}
void drw_batch_cache_generate_requested(Object *ob, TaskGraph &task_graph)
{
const DRWContext *draw_ctx = DRW_context_get();
const Scene *scene = draw_ctx->scene;
const enum eContextObjectMode mode = CTX_data_mode_enum_ex(
draw_ctx->object_edit, draw_ctx->obact, draw_ctx->object_mode);
const bool is_paint_mode = ELEM(
mode, CTX_MODE_SCULPT, CTX_MODE_PAINT_TEXTURE, CTX_MODE_PAINT_VERTEX, CTX_MODE_PAINT_WEIGHT);
const bool use_hide = ((ob->type == OB_MESH) &&
((is_paint_mode && (ob == draw_ctx->obact) &&
DRW_object_use_hide_faces(ob)) ||
((mode == CTX_MODE_EDIT_MESH) && (ob->mode == OB_MODE_EDIT))));
switch (ob->type) {
case OB_MESH:
DRW_mesh_batch_cache_create_requested(task_graph,
*ob,
DRW_object_get_data_for_drawing<Mesh>(*ob),
*scene,
is_paint_mode,
use_hide);
break;
case OB_CURVES_LEGACY:
case OB_FONT:
case OB_SURF:
DRW_curve_batch_cache_create_requested(ob, scene);
break;
case OB_CURVES:
DRW_curves_batch_cache_create_requested(ob);
break;
case OB_POINTCLOUD:
DRW_pointcloud_batch_cache_create_requested(ob);
break;
/* TODO: all cases. */
default:
break;
}
}
void drw_batch_cache_generate_requested_evaluated_mesh_or_curve(Object *ob, TaskGraph &task_graph)
{
/* NOTE: Logic here is duplicated from #drw_batch_cache_generate_requested. */
const DRWContext *draw_ctx = DRW_context_get();
const Scene *scene = draw_ctx->scene;
const enum eContextObjectMode mode = CTX_data_mode_enum_ex(
draw_ctx->object_edit, draw_ctx->obact, draw_ctx->object_mode);
const bool is_paint_mode = ELEM(
mode, CTX_MODE_SCULPT, CTX_MODE_PAINT_TEXTURE, CTX_MODE_PAINT_VERTEX, CTX_MODE_PAINT_WEIGHT);
if (ob->type == OB_MESH) {
const bool use_hide = ((is_paint_mode && (ob == draw_ctx->obact) &&
DRW_object_use_hide_faces(ob)) ||
((mode == CTX_MODE_EDIT_MESH) && (ob->mode == OB_MODE_EDIT)));
if (Mesh *mesh = BKE_object_get_evaluated_mesh_no_subsurf_unchecked(ob)) {
DRW_mesh_batch_cache_create_requested(
task_graph, *ob, DRW_mesh_get_for_drawing(*mesh), *scene, is_paint_mode, use_hide);
}
}
else if (Mesh *mesh = BKE_object_get_evaluated_mesh_no_subsurf_unchecked(ob)) {
/* Legacy curve objects can have evaluated mesh data while the original object is still a curve
* in edit mode (see #156971). #DRW_mesh_batch_cache_create_requested expects the object to
* match the mesh data and to be in object mode for non-edit mesh extraction, so use a shallow
* temporary mesh object for drawing. */
bke::ObjectRuntime tmp_runtime = *ob->runtime;
Object tmp_object = dna::shallow_copy(*ob);
tmp_object.runtime = &tmp_runtime;
tmp_object.mode = OB_MODE_OBJECT;
BKE_object_replace_data_on_shallow_copy(&tmp_object, &mesh->id);
DRW_mesh_batch_cache_create_requested(
task_graph, tmp_object, DRW_mesh_get_for_drawing(*mesh), *scene, is_paint_mode, false);
}
else if (ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) {
DRW_curve_batch_cache_create_requested(ob, scene);
}
}
void drw_batch_cache_generate_requested_delayed(Object *ob)
{
DRWContext &draw_ctx = drw_get();
draw_ctx.delayed_extraction.add(ob);
}
void DRW_batch_cache_free_old(Object *ob, int ctime)
{
switch (ob->type) {
case OB_MESH:
DRW_mesh_batch_cache_free_old(&DRW_object_get_data_for_drawing<Mesh>(*ob), ctime);
break;
case OB_CURVES:
DRW_curves_batch_cache_free_old(&DRW_object_get_data_for_drawing<Curves>(*ob), ctime);
break;
case OB_POINTCLOUD:
DRW_pointcloud_batch_cache_free_old(&DRW_object_get_data_for_drawing<PointCloud>(*ob),
ctime);
break;
default:
break;
}
}
/** \} */
void DRW_cdlayer_attr_aliases_add(GPUVertFormat *format,
const char *base_name,
const bke::AttrType data_type,
const StringRef layer_name,
bool is_active_render,
bool is_active_layer)
{
char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
GPU_vertformat_safe_attr_name(layer_name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
/* Attribute layer name. */
SNPRINTF(attr_name, "%s%s", base_name, attr_safe_name);
GPU_vertformat_alias_add(format, attr_name);
/* Auto layer name. */
SNPRINTF(attr_name, "a%s", attr_safe_name);
GPU_vertformat_alias_add(format, attr_name);
/* Active render layer name. */
if (is_active_render) {
GPU_vertformat_alias_add(format, data_type == bke::AttrType::Float2 ? "a" : base_name);
}
/* Active display layer name. */
if (is_active_layer) {
SNPRINTF(attr_name, "a%s", base_name);
GPU_vertformat_alias_add(format, attr_name);
}
}
} // namespace blender::draw

View File

@@ -0,0 +1,165 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "BLI_math_matrix_types.hh"
#include "BLI_span.hh"
#include "BKE_volume_grid_fwd.hh"
namespace blender {
struct GPUMaterial;
namespace gpu {
class Texture;
class Batch;
class VertBuf;
} // namespace gpu
struct ModifierData;
struct Object;
struct PTCacheEdit;
struct ParticleSystem;
struct Volume;
struct Scene;
namespace draw {
/**
* Shape resolution level of detail.
*/
enum eDRWLevelOfDetail {
DRW_LOD_LOW = 0,
DRW_LOD_MEDIUM = 1,
DRW_LOD_HIGH = 2,
DRW_LOD_MAX, /* Max number of level of detail */
};
/* Common Object */
gpu::Batch *DRW_cache_object_all_edges_get(Object *ob);
gpu::Batch *DRW_cache_object_edge_detection_get(Object *ob, bool *r_is_manifold);
gpu::Batch *DRW_cache_object_surface_get(Object *ob);
gpu::Batch *DRW_cache_object_loose_edges_get(Object *ob);
Span<gpu::Batch *> DRW_cache_object_surface_material_get(Object *ob,
Span<const GPUMaterial *> materials);
gpu::Batch *DRW_cache_object_face_wireframe_get(const Scene *scene, Object *ob);
/* Meshes */
gpu::Batch *DRW_cache_mesh_all_verts_get(Object *ob);
gpu::Batch *DRW_cache_mesh_paint_overlay_verts_get(Object *ob);
gpu::Batch *DRW_cache_mesh_all_edges_get(Object *ob);
gpu::Batch *DRW_cache_mesh_loose_edges_get(Object *ob);
gpu::Batch *DRW_cache_mesh_edge_detection_get(Object *ob, bool *r_is_manifold);
gpu::Batch *DRW_cache_mesh_surface_get(Object *ob);
gpu::Batch *DRW_cache_mesh_paint_overlay_surface_get(Object *ob);
gpu::Batch *DRW_cache_mesh_paint_overlay_edges_get(Object *ob);
/**
* Return list of batches with length equal to `max(1, totcol)`.
*/
Span<gpu::Batch *> DRW_cache_mesh_surface_shaded_get(Object *ob,
Span<const GPUMaterial *> materials);
/**
* Return list of batches with length equal to `max(1, totcol)`.
*/
Span<gpu::Batch *> DRW_cache_mesh_surface_texpaint_get(Object *ob);
gpu::Batch *DRW_cache_mesh_surface_texpaint_single_get(Object *ob);
gpu::Batch *DRW_cache_mesh_surface_vertpaint_get(Object *ob);
gpu::Batch *DRW_cache_mesh_surface_sculptcolors_get(Object *ob);
gpu::Batch *DRW_cache_mesh_surface_weights_get(Object *ob);
gpu::Batch *DRW_cache_mesh_surface_mesh_analysis_get(Object *ob);
gpu::Batch *DRW_cache_mesh_face_wireframe_get(Object *ob);
gpu::Batch *DRW_cache_mesh_surface_viewer_attribute_get(Object *ob);
/* Curve */
gpu::Batch *DRW_cache_curve_edge_wire_get(Object *ob);
gpu::Batch *DRW_cache_curve_edge_wire_viewer_attribute_get(Object *ob);
/* edit-mode */
gpu::Batch *DRW_cache_curve_edge_normal_get(Object *ob);
gpu::Batch *DRW_cache_curve_edge_overlay_get(Object *ob);
gpu::Batch *DRW_cache_curve_vert_overlay_get(Object *ob);
/* Font */
gpu::Batch *DRW_cache_text_edge_wire_get(Object *ob);
/* Surface */
gpu::Batch *DRW_cache_surf_edge_wire_get(Object *ob);
/* Lattice */
gpu::Batch *DRW_cache_lattice_verts_get(Object *ob);
gpu::Batch *DRW_cache_lattice_wire_get(Object *ob, bool use_weight);
gpu::Batch *DRW_cache_lattice_vert_overlay_get(Object *ob);
/* Point Cloud */
gpu::Batch *DRW_cache_pointcloud_vert_overlay_get(Object *ob);
/* Particles */
gpu::Batch *DRW_cache_particles_get_hair(Object *object, ParticleSystem *psys, ModifierData *md);
gpu::Batch *DRW_cache_particles_get_dots(Object *object, ParticleSystem *psys);
gpu::Batch *DRW_cache_particles_get_edit_strands(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit,
bool use_weight);
gpu::Batch *DRW_cache_particles_get_edit_inner_points(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit);
gpu::Batch *DRW_cache_particles_get_edit_tip_points(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit);
/* Volume */
struct DRWVolumeGrid {
DRWVolumeGrid *next, *prev;
/* Grid name. */
char *name;
/* 3D texture. */
gpu::Texture *texture;
/* Transform between 0..1 texture space and object space. */
float4x4 texture_to_object;
float4x4 object_to_texture;
/* Transform from bounds to texture space. */
float4x4 object_to_bounds;
float4x4 bounds_to_texture;
};
DRWVolumeGrid *DRW_volume_batch_cache_get_grid(Volume *volume,
const bke::VolumeGridData *volume_grid);
gpu::Batch *DRW_cache_volume_face_wireframe_get(Object *ob);
gpu::Batch *DRW_cache_volume_selection_surface_get(Object *ob);
/* Grease Pencil */
/* When there's no visible drawings in this grease pencil object, the returned `Batch` could be
* nullptr as `grease_pencil_edit_batch_ensure` won't do anything in those cases. */
gpu::Batch *DRW_cache_grease_pencil_get(const Scene *scene, Object *ob);
gpu::Batch *DRW_cache_grease_pencil_edit_points_get(const Scene *scene, Object *ob);
gpu::Batch *DRW_cache_grease_pencil_edit_lines_get(const Scene *scene, Object *ob);
gpu::Batch *DRW_cache_grease_pencil_edit_handles_get(const Scene *scene, Object *ob);
gpu::VertBuf *DRW_cache_grease_pencil_position_buffer_get(const Scene *scene, Object *ob);
gpu::VertBuf *DRW_cache_grease_pencil_color_buffer_get(const Scene *scene, Object *ob);
gpu::Batch *DRW_cache_grease_pencil_weight_points_get(const Scene *scene, Object *ob);
gpu::Batch *DRW_cache_grease_pencil_weight_lines_get(const Scene *scene, Object *ob);
gpu::Batch *DRW_cache_grease_pencil_face_wireframe_get(const Scene *scene, Object *ob);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,353 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "BLI_array.hh"
#include "BLI_enum_flags.hh"
#include "BLI_map.hh"
#include "BLI_math_matrix_types.hh"
#include "DNA_view3d_enums.h"
#include "GPU_index_buffer.hh"
#include "GPU_shader.hh"
#include "GPU_vertex_buffer.hh"
#include "draw_attributes.hh"
namespace blender {
namespace gpu {
class Batch;
class IndexBuf;
} // namespace gpu
struct Mesh;
struct Object;
struct Scene;
struct TaskGraph;
struct ToolSettings;
namespace draw {
struct MeshRenderData;
struct DRWSubdivCache;
/* Vertex Group Selection and display options */
struct DRW_MeshWeightState {
int defgroup_active;
int defgroup_len;
short flags;
char alert_mode;
/* Set of all selected bones for Multi-paint. */
bool *defgroup_sel; /* #defgroup_len */
int defgroup_sel_count;
/* Set of all locked and unlocked deform bones for Lock Relative mode. */
bool *defgroup_locked; /* #defgroup_len */
bool *defgroup_unlocked; /* #defgroup_len */
};
/* DRW_MeshWeightState.flags */
enum {
DRW_MESH_WEIGHT_STATE_MULTIPAINT = (1 << 0),
DRW_MESH_WEIGHT_STATE_AUTO_NORMALIZE = (1 << 1),
DRW_MESH_WEIGHT_STATE_LOCK_RELATIVE = (1 << 2),
};
/**
* Vertex buffer types that can be use by batches in the mesh batch cache.
*
* \todo It would be good to change this to something like #draw::pbvh::AttributeRequest to
* separate the generic attribute requests. While there is a limit on the number of vertex buffers
* used by a single shader/batch, there is no need for that limit here; there are potentially many
* shaders requiring attributes for a particular mesh. OTOH, it may be good to use flags for the
* builtin buffer types, so that bitwise operations can be used.
*/
enum class VBOType : int8_t {
Position,
CornerNormal,
EdgeFactor,
VertexGroupWeight,
UVs,
Tangents,
SculptData,
Orco,
EditData,
EditUVData,
EditUVStretchArea,
EditUVStretchAngle,
MeshAnalysis,
FaceDotPosition,
FaceDotNormal,
FaceDotUV,
FaceDotEditUVData,
SkinRoots,
IndexVert,
IndexEdge,
IndexFace,
IndexFaceDot,
Attr0,
Attr1,
Attr2,
Attr3,
Attr5,
Attr6,
Attr7,
Attr8,
Attr9,
Attr10,
Attr11,
Attr12,
Attr13,
Attr14,
Attr15,
AttrViewer,
VertexNormal,
PaintOverlayFlag,
};
/**
* All index buffers used for mesh batches.
*
* \note "Tris per material" (#MeshBatchCache::tris_per_mat) is an exception. Since there are
* an arbitrary numbers of materials, those are handled separately (as slices of the overall
* triangles buffer).
*/
enum class IBOType : int8_t {
Tris,
Lines,
LinesLoose,
Points,
FaceDots,
LinesPaintMask,
LinesAdjacency,
UVTris,
AllUVLines,
UVLines,
EditUVTris,
EditUVLines,
EditUVPoints,
EditUVFaceDots,
};
struct MeshBufferList {
/* Though using maps here may add some overhead compared to just indexed arrays, it's a bit more
* convenient currently, because the "buffer exists" test is very clear, it's just whether the
* map contains it (e.g. compared to "buffer is allocated but not filled with data"). The
* sparseness *may* be useful for reducing memory usage when only few buffers are used. */
Map<VBOType, std::unique_ptr<gpu::VertBuf, gpu::VertBufDeleter>> vbos;
Map<IBOType, std::unique_ptr<gpu::IndexBuf, gpu::IndexBufDeleter>> ibos;
};
struct MeshBatchList {
/* Surfaces / Render */
gpu::Batch *surface;
gpu::Batch *surface_weights;
/* Edit mode */
gpu::Batch *edit_triangles;
gpu::Batch *edit_vertices;
gpu::Batch *edit_edges;
gpu::Batch *edit_vnor;
gpu::Batch *edit_lnor;
gpu::Batch *edit_fdots;
gpu::Batch *edit_mesh_analysis;
gpu::Batch *edit_skin_roots;
/* Edit UVs */
gpu::Batch *edituv_faces_stretch_area;
gpu::Batch *edituv_faces_stretch_angle;
gpu::Batch *edituv_faces;
gpu::Batch *edituv_edges;
gpu::Batch *edituv_verts;
gpu::Batch *edituv_fdots;
/* Edit selection */
gpu::Batch *edit_selection_verts;
gpu::Batch *edit_selection_edges;
gpu::Batch *edit_selection_faces;
gpu::Batch *edit_selection_fdots;
/* Common display / Other */
gpu::Batch *uv_faces;
gpu::Batch *all_verts;
gpu::Batch *all_edges;
gpu::Batch *loose_edges;
gpu::Batch *edge_detection;
/* Individual edges with face normals. */
gpu::Batch *wire_edges;
/* Loops around faces. no edges between selected faces */
gpu::Batch *paint_overlay_wire_loops;
gpu::Batch *wire_loops_all_uvs;
gpu::Batch *wire_loops_uvs;
gpu::Batch *wire_loops_edituvs;
gpu::Batch *sculpt_overlays;
gpu::Batch *surface_viewer_attribute;
gpu::Batch *paint_overlay_verts;
gpu::Batch *paint_overlay_surface;
};
#define MBC_BATCH_LEN (sizeof(MeshBatchList) / sizeof(void *))
#define MBC_BATCH_INDEX(batch) (offsetof(MeshBatchList, batch) / sizeof(void *))
enum DRWBatchFlag : uint64_t {
MBC_SURFACE = (1u << MBC_BATCH_INDEX(surface)),
MBC_SURFACE_WEIGHTS = (1u << MBC_BATCH_INDEX(surface_weights)),
MBC_EDIT_TRIANGLES = (1u << MBC_BATCH_INDEX(edit_triangles)),
MBC_EDIT_VERTICES = (1u << MBC_BATCH_INDEX(edit_vertices)),
MBC_EDIT_EDGES = (1u << MBC_BATCH_INDEX(edit_edges)),
MBC_EDIT_VNOR = (1u << MBC_BATCH_INDEX(edit_vnor)),
MBC_EDIT_LNOR = (1u << MBC_BATCH_INDEX(edit_lnor)),
MBC_EDIT_FACEDOTS = (1u << MBC_BATCH_INDEX(edit_fdots)),
MBC_EDIT_MESH_ANALYSIS = (1u << MBC_BATCH_INDEX(edit_mesh_analysis)),
MBC_SKIN_ROOTS = (1u << MBC_BATCH_INDEX(edit_skin_roots)),
MBC_UV_FACES = (1u << MBC_BATCH_INDEX(uv_faces)),
MBC_EDITUV_FACES_STRETCH_AREA = (1u << MBC_BATCH_INDEX(edituv_faces_stretch_area)),
MBC_EDITUV_FACES_STRETCH_ANGLE = (1u << MBC_BATCH_INDEX(edituv_faces_stretch_angle)),
MBC_EDITUV_FACES = (1u << MBC_BATCH_INDEX(edituv_faces)),
MBC_EDITUV_EDGES = (1u << MBC_BATCH_INDEX(edituv_edges)),
MBC_EDITUV_VERTS = (1u << MBC_BATCH_INDEX(edituv_verts)),
MBC_EDITUV_FACEDOTS = (1u << MBC_BATCH_INDEX(edituv_fdots)),
MBC_EDIT_SELECTION_VERTS = (1u << MBC_BATCH_INDEX(edit_selection_verts)),
MBC_EDIT_SELECTION_EDGES = (1u << MBC_BATCH_INDEX(edit_selection_edges)),
MBC_EDIT_SELECTION_FACES = (1u << MBC_BATCH_INDEX(edit_selection_faces)),
MBC_EDIT_SELECTION_FACEDOTS = (1u << MBC_BATCH_INDEX(edit_selection_fdots)),
MBC_ALL_VERTS = (1u << MBC_BATCH_INDEX(all_verts)),
MBC_ALL_EDGES = (1u << MBC_BATCH_INDEX(all_edges)),
MBC_LOOSE_EDGES = (1u << MBC_BATCH_INDEX(loose_edges)),
MBC_EDGE_DETECTION = (1u << MBC_BATCH_INDEX(edge_detection)),
MBC_WIRE_EDGES = (1u << MBC_BATCH_INDEX(wire_edges)),
MBC_PAINT_OVERLAY_WIRE_LOOPS = (1u << MBC_BATCH_INDEX(paint_overlay_wire_loops)),
MBC_WIRE_LOOPS_ALL_UVS = (1u << MBC_BATCH_INDEX(wire_loops_all_uvs)),
MBC_WIRE_LOOPS_UVS = (1u << MBC_BATCH_INDEX(wire_loops_uvs)),
MBC_WIRE_LOOPS_EDITUVS = (1u << MBC_BATCH_INDEX(wire_loops_edituvs)),
MBC_SCULPT_OVERLAYS = (1u << MBC_BATCH_INDEX(sculpt_overlays)),
MBC_VIEWER_ATTRIBUTE_OVERLAY = (1u << MBC_BATCH_INDEX(surface_viewer_attribute)),
MBC_PAINT_OVERLAY_VERTS = (uint64_t(1u) << MBC_BATCH_INDEX(paint_overlay_verts)),
MBC_PAINT_OVERLAY_SURFACE = (uint64_t(1u) << MBC_BATCH_INDEX(paint_overlay_surface)),
MBC_SURFACE_PER_MAT = (uint64_t(1u) << MBC_BATCH_LEN),
};
ENUM_OPERATORS(DRWBatchFlag);
BLI_STATIC_ASSERT(MBC_BATCH_LEN < 64, "Number of batches exceeded the limit of bit fields");
struct MeshExtractLooseGeom {
/** Indices of all vertices not used by edges in the #Mesh or #BMesh. */
IndexMask verts;
/** Indices of all edges not used by faces in the #Mesh or #BMesh. */
IndexMask edges;
/** Used for BMesh which does not cache loose geometry index masks. */
std::unique_ptr<LinearAllocator<>> allocator;
};
struct SortedFaceData {
/* The total number of visible triangles (a sum of the values in #mat_tri_counts). */
int visible_tris_num;
/** The number of visible triangles assigned to each material. */
Array<int> tris_num_by_material;
/**
* The first triangle index for each face, sorted into slices by material.
* May be empty if the mesh only has a single material.
*/
std::optional<Array<int>> face_tri_offsets;
};
/**
* Data that are kept around between extractions to reduce rebuilding time.
*
* - Loose geometry.
*/
struct MeshBufferCache {
MeshBufferList buff;
MeshExtractLooseGeom loose_geom;
SortedFaceData face_sorted;
};
#define FOREACH_MESH_BUFFER_CACHE(batch_cache, mbc) \
for (MeshBufferCache *mbc = &batch_cache.final; \
mbc == &batch_cache.final || mbc == &batch_cache.cage || mbc == &batch_cache.uv_cage; \
mbc = (mbc == &batch_cache.final) ? \
&batch_cache.cage : \
((mbc == &batch_cache.cage) ? &batch_cache.uv_cage : nullptr))
struct MeshBatchCache {
MeshBufferCache final, cage, uv_cage;
MeshBatchList batch;
/* Index buffer per material. These are sub-ranges of `ibo.tris`. */
Array<gpu::IndexBufPtr> tris_per_mat;
Array<gpu::Batch *> surface_per_mat;
DRWSubdivCache *subdiv_cache;
DRWBatchFlag batch_requested;
DRWBatchFlag batch_ready;
/* Settings to determine if cache is invalid. */
int edge_len;
int tri_len;
int face_len;
int vert_len;
int mat_len;
/* Instantly invalidates cache, skipping mesh check */
bool is_dirty;
bool is_editmode;
bool is_uvsyncsel;
DRW_MeshWeightState weight_state;
DRW_MeshCDMask cd_used, cd_needed, cd_used_over_time;
VectorSet<std::string> attr_used, attr_needed, attr_used_over_time;
int lastmatch;
/* Valid only if edge_detection is up to date. */
bool is_manifold;
bool no_loose_wire;
/* Total areas for drawing UV Stretching. Contains the summed area in mesh
* space (`tot_area`) and the summed area in uv space (`tot_uvarea`).
*
* Only valid after `DRW_mesh_batch_cache_create_requested` has been called. */
float tot_area, tot_uv_area;
};
#define MBC_EDITUV \
(MBC_EDITUV_FACES_STRETCH_AREA | MBC_EDITUV_FACES_STRETCH_ANGLE | MBC_EDITUV_FACES | \
MBC_EDITUV_EDGES | MBC_EDITUV_VERTS | MBC_EDITUV_FACEDOTS | MBC_UV_FACES | \
MBC_WIRE_LOOPS_ALL_UVS | MBC_WIRE_LOOPS_UVS | MBC_WIRE_LOOPS_EDITUVS)
void mesh_buffer_cache_create_requested(TaskGraph &task_graph,
const Scene &scene,
MeshBatchCache &cache,
MeshBufferCache &mbc,
Span<IBOType> ibo_requests,
Span<VBOType> vbo_requests,
Object &object,
Mesh &mesh,
bool is_editmode,
bool is_paint_mode,
bool do_final,
bool do_uvedit,
bool use_hide);
void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache &cache,
MeshBufferCache &mbc,
Span<IBOType> ibo_requests,
Span<VBOType> vbo_requests,
DRWSubdivCache &subdiv_cache,
MeshRenderData &mr);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,524 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Extraction of Mesh data into VBO to feed to GPU.
*/
#include "BKE_attribute.hh"
#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_task.hh"
#include "GPU_capabilities.hh"
#include "GPU_debug.hh"
#include "draw_cache_extract.hh"
#include "draw_subdivision.hh"
#include "mesh_extractors/extract_mesh.hh"
// #define DEBUG_TIME
#ifdef DEBUG_TIME
# include "BLI_time_utildefines.h"
#endif
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Dependency Data
* \{ */
static void ensure_dependency_data(MeshRenderData &mr,
Span<IBOType> ibo_requests,
Span<VBOType> vbo_requests,
MeshBufferCache &cache)
{
const bool request_face_normals = vbo_requests.contains(VBOType::CornerNormal) ||
vbo_requests.contains(VBOType::FaceDotNormal) ||
vbo_requests.contains(VBOType::EdgeFactor) ||
vbo_requests.contains(VBOType::MeshAnalysis);
const bool request_corner_normals = vbo_requests.contains(VBOType::CornerNormal);
const bool force_corner_normals = vbo_requests.contains(VBOType::Tangents);
if (request_face_normals) {
mesh_render_data_update_face_normals(mr);
}
if ((request_corner_normals && mr.normals_domain == bke::MeshNormalDomain::Corner &&
!mr.use_simplify_normals) ||
force_corner_normals)
{
mesh_render_data_update_corner_normals(mr);
}
const bool calc_loose_geom = ibo_requests.contains(IBOType::Lines) ||
ibo_requests.contains(IBOType::LinesLoose) ||
ibo_requests.contains(IBOType::Points) ||
vbo_requests.contains(VBOType::Position) ||
vbo_requests.contains(VBOType::EditData) ||
vbo_requests.contains(VBOType::VertexNormal) ||
vbo_requests.contains(VBOType::IndexVert) ||
vbo_requests.contains(VBOType::IndexEdge) ||
vbo_requests.contains(VBOType::EdgeFactor);
if (calc_loose_geom) {
mesh_render_data_update_loose_geom(mr, cache);
}
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Extract Loop
* \{ */
/**
* The mesh normals access functions can end up mixing face corner normals calculated with the
* costly tangent space method. The "Simplify Normals" option is supposed to avoid that, but not
* the "Free" normals which are actually cheaper than calculating true normals.
*/
static bool use_normals_simplify(const Scene &scene, const MeshRenderData &mr)
{
if (!(scene.r.mode & R_SIMPLIFY) || !(scene.r.mode & R_SIMPLIFY_NORMALS)) {
return false;
}
if (!mr.mesh) {
return true;
}
const Mesh &mesh = *mr.mesh;
const std::optional<bke::AttributeMetaData> meta_data = mesh.attributes().lookup_meta_data(
"custom_normal");
if (!meta_data) {
return false;
}
if (meta_data->domain == bke::AttrDomain::Corner &&
meta_data->data_type == bke::AttrType::Int16_2D)
{
return true;
}
return false;
}
void mesh_buffer_cache_create_requested(TaskGraph & /*task_graph*/,
const Scene &scene,
MeshBatchCache &cache,
MeshBufferCache &mbc,
const Span<IBOType> ibo_requests,
const Span<VBOType> vbo_requests,
Object &object,
Mesh &mesh,
const bool is_editmode,
const bool is_paint_mode,
const bool do_final,
const bool do_uvedit,
const bool use_hide)
{
if (ibo_requests.is_empty() && vbo_requests.is_empty()) {
return;
}
MeshBufferList &buffers = mbc.buff;
Vector<IBOType, 16> ibos_to_create;
for (const IBOType request : ibo_requests) {
if (!buffers.ibos.contains(request)) {
ibos_to_create.append(request);
}
}
Vector<VBOType, 16> vbos_to_create;
for (const VBOType request : vbo_requests) {
if (!buffers.vbos.contains(request)) {
vbos_to_create.append(request);
}
}
if (ibos_to_create.is_empty() && vbos_to_create.is_empty()) {
return;
}
#ifdef DEBUG_TIME
SCOPED_TIMER(__func__);
#endif
MeshRenderData mr = mesh_render_data_create(
object, mesh, is_editmode, is_paint_mode, do_final, do_uvedit, use_hide, scene.toolsettings);
mr.use_subsurf_fdots = mr.mesh && !mr.mesh->runtime->subsurf_face_dot_tags.is_empty();
mr.use_simplify_normals = use_normals_simplify(scene, mr);
ensure_dependency_data(mr, ibo_requests, vbo_requests, mbc);
Array<gpu::IndexBufPtr, 16> created_ibos(ibos_to_create.size());
{
/* Because lines and loose lines are stored in the same buffer, they're handled separately
* rather than from potentially multiple threads in the parallel_for_each loop below. */
const int lines_index = ibos_to_create.as_span().first_index_try(IBOType::Lines);
const int loose_lines_index = ibos_to_create.as_span().first_index_try(IBOType::LinesLoose);
if (lines_index != -1 || loose_lines_index != -1) {
extract_lines(mr,
lines_index == -1 ? nullptr : &created_ibos[lines_index],
loose_lines_index == -1 ? nullptr : &created_ibos[loose_lines_index],
cache.no_loose_wire);
}
}
threading::parallel_for_each(ibos_to_create.index_range(), [&](const int i) {
switch (ibos_to_create[i]) {
case IBOType::Tris:
created_ibos[i] = extract_tris(mr, mesh_render_data_faces_sorted_ensure(mr, mbc));
break;
case IBOType::Lines:
case IBOType::LinesLoose:
/* Handled as a special case above. */
break;
case IBOType::Points:
created_ibos[i] = extract_points(mr);
break;
case IBOType::FaceDots:
created_ibos[i] = extract_face_dots(mr);
break;
case IBOType::LinesPaintMask:
created_ibos[i] = extract_lines_paint_mask(mr);
break;
case IBOType::LinesAdjacency:
created_ibos[i] = extract_lines_adjacency(mr, cache.is_manifold);
break;
case IBOType::UVTris:
created_ibos[i] = extract_edituv_tris(mr, false);
break;
case IBOType::EditUVTris:
created_ibos[i] = extract_edituv_tris(mr, true);
break;
case IBOType::AllUVLines:
created_ibos[i] = extract_edituv_lines(mr, UvExtractionMode::All);
break;
case IBOType::UVLines:
created_ibos[i] = extract_edituv_lines(mr, UvExtractionMode::Selection);
break;
case IBOType::EditUVLines:
created_ibos[i] = extract_edituv_lines(mr, UvExtractionMode::Edit);
break;
case IBOType::EditUVPoints:
created_ibos[i] = extract_edituv_points(mr);
break;
case IBOType::EditUVFaceDots:
created_ibos[i] = extract_edituv_face_dots(mr);
break;
}
});
Array<gpu::VertBufPtr, 16> created_vbos(vbos_to_create.size());
const bool do_hq_normals = (scene.r.perf_flag & SCE_PERF_HQ_NORMALS) != 0 ||
GPU_use_hq_normals_workaround();
threading::parallel_for_each(vbos_to_create.index_range(), [&](const int i) {
switch (vbos_to_create[i]) {
case VBOType::Position:
created_vbos[i] = extract_positions(mr);
break;
case VBOType::CornerNormal:
created_vbos[i] = extract_normals(mr, do_hq_normals);
break;
case VBOType::EdgeFactor:
created_vbos[i] = extract_edge_factor(mr);
break;
case VBOType::VertexGroupWeight:
created_vbos[i] = extract_weights(mr, cache);
break;
case VBOType::UVs:
created_vbos[i] = extract_uv_maps(mr, cache);
break;
case VBOType::Tangents:
created_vbos[i] = extract_tangents(mr, cache, do_hq_normals);
break;
case VBOType::SculptData:
created_vbos[i] = extract_sculpt_data(mr);
break;
case VBOType::Orco:
created_vbos[i] = extract_orco(mr);
break;
case VBOType::EditData:
created_vbos[i] = extract_edit_data(mr);
break;
case VBOType::EditUVData:
created_vbos[i] = extract_edituv_data(mr);
break;
case VBOType::EditUVStretchArea:
created_vbos[i] = extract_edituv_stretch_area(mr, cache.tot_area, cache.tot_uv_area);
break;
case VBOType::EditUVStretchAngle:
created_vbos[i] = extract_edituv_stretch_angle(mr);
break;
case VBOType::MeshAnalysis:
created_vbos[i] = extract_mesh_analysis(mr, object.object_to_world());
break;
case VBOType::FaceDotPosition:
created_vbos[i] = extract_face_dots_position(mr);
break;
case VBOType::FaceDotNormal:
created_vbos[i] = extract_face_dot_normals(mr, do_hq_normals);
break;
case VBOType::FaceDotUV:
created_vbos[i] = extract_face_dots_uv(mr);
break;
case VBOType::FaceDotEditUVData:
created_vbos[i] = extract_face_dots_edituv_data(mr);
break;
case VBOType::SkinRoots:
created_vbos[i] = extract_skin_roots(mr);
break;
case VBOType::IndexVert:
created_vbos[i] = extract_vert_index(mr);
break;
case VBOType::IndexEdge:
created_vbos[i] = extract_edge_index(mr);
break;
case VBOType::IndexFace:
created_vbos[i] = extract_face_index(mr);
break;
case VBOType::IndexFaceDot:
created_vbos[i] = extract_face_dot_index(mr);
break;
case VBOType::Attr0:
case VBOType::Attr1:
case VBOType::Attr2:
case VBOType::Attr3:
case VBOType::Attr5:
case VBOType::Attr6:
case VBOType::Attr7:
case VBOType::Attr8:
case VBOType::Attr9:
case VBOType::Attr10:
case VBOType::Attr11:
case VBOType::Attr12:
case VBOType::Attr13:
case VBOType::Attr14:
case VBOType::Attr15: {
const int8_t attr_index = int8_t(vbos_to_create[i]) - int8_t(VBOType::Attr0);
created_vbos[i] = extract_attribute(mr, cache.attr_used[attr_index]);
break;
}
case VBOType::AttrViewer:
created_vbos[i] = extract_attr_viewer(mr);
break;
case VBOType::VertexNormal:
created_vbos[i] = extract_vert_normals(mr);
break;
case VBOType::PaintOverlayFlag:
created_vbos[i] = extract_paint_overlay_flags(mr);
break;
}
});
for (const int i : ibos_to_create.index_range()) {
buffers.ibos.add_new(ibos_to_create[i], std::move(created_ibos[i]));
}
for (const int i : vbos_to_create.index_range()) {
buffers.vbos.add_new(vbos_to_create[i], std::move(created_vbos[i]));
}
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Subdivision Extract Loop
* \{ */
void mesh_buffer_cache_create_requested_subdiv(MeshBatchCache &cache,
MeshBufferCache &mbc,
const Span<IBOType> ibo_requests,
const Span<VBOType> vbo_requests,
DRWSubdivCache &subdiv_cache,
MeshRenderData &mr)
{
if (ibo_requests.is_empty() && vbo_requests.is_empty()) {
return;
}
MeshBufferList &buffers = mbc.buff;
mesh_render_data_update_corner_normals(mr);
mesh_render_data_update_loose_geom(mr, mbc);
DRW_subdivide_loose_geom(subdiv_cache, mbc);
Set<IBOType, 16> ibos_to_create;
for (const IBOType request : ibo_requests) {
if (!buffers.ibos.contains(request)) {
ibos_to_create.add_new(request);
}
}
Set<VBOType, 16> vbos_to_create;
for (const VBOType request : vbo_requests) {
if (!buffers.vbos.contains(request)) {
vbos_to_create.add_new(request);
}
}
if (ibos_to_create.is_empty() && vbos_to_create.is_empty()) {
return;
}
static gpu::DebugScope subdiv_extract_scope = {"SubdivExtraction"};
auto capture = subdiv_extract_scope.scoped_capture();
if (vbos_to_create.contains(VBOType::Position) || vbos_to_create.contains(VBOType::Orco)) {
gpu::VertBufPtr orco_vbo;
/* Don't use `add_new` because #VBOType::Orco might be requested after #VBOType::Position
* already exists. It's inefficient to build the position VBO a second time but that's the API
* that GPU subdivision provides. */
buffers.vbos.add(
VBOType::Position,
extract_positions_subdiv(
subdiv_cache, mr, vbos_to_create.contains(VBOType::Orco) ? &orco_vbo : nullptr));
if (orco_vbo) {
buffers.vbos.add_new(VBOType::Orco, std::move(orco_vbo));
}
}
if (vbos_to_create.contains(VBOType::CornerNormal)) {
/* The corner normals calculation uses positions and normals stored in the `pos` VBO. */
buffers.vbos.add_new(
VBOType::CornerNormal,
extract_normals_subdiv(mr, subdiv_cache, *buffers.vbos.lookup(VBOType::Position)));
}
if (vbos_to_create.contains(VBOType::EdgeFactor)) {
buffers.vbos.add_new(
VBOType::EdgeFactor,
extract_edge_factor_subdiv(subdiv_cache, mr, *buffers.vbos.lookup(VBOType::Position)));
}
if (ibos_to_create.contains(IBOType::Lines) || ibos_to_create.contains(IBOType::LinesLoose)) {
gpu::IndexBufPtr lines_ibo;
gpu::IndexBufPtr lines_loose_ibo;
extract_lines_subdiv(subdiv_cache,
mr,
ibos_to_create.contains(IBOType::Lines) ? &lines_ibo : nullptr,
ibos_to_create.contains(IBOType::LinesLoose) ? &lines_loose_ibo : nullptr,
cache.no_loose_wire);
if (lines_ibo) {
buffers.ibos.add_new(IBOType::Lines, std::move(lines_ibo));
}
if (lines_loose_ibo) {
buffers.ibos.add_new(IBOType::LinesLoose, std::move(lines_loose_ibo));
}
}
if (ibos_to_create.contains(IBOType::Tris)) {
buffers.ibos.add_new(IBOType::Tris, extract_tris_subdiv(subdiv_cache, cache));
}
if (ibos_to_create.contains(IBOType::Points)) {
buffers.ibos.add_new(IBOType::Points, extract_points_subdiv(mr, subdiv_cache));
}
if (vbos_to_create.contains(VBOType::EditData)) {
buffers.vbos.add_new(VBOType::EditData, extract_edit_data_subdiv(mr, subdiv_cache));
}
if (vbos_to_create.contains(VBOType::Tangents)) {
buffers.vbos.add_new(VBOType::Tangents, extract_tangents_subdiv(mr, subdiv_cache, cache));
}
if (vbos_to_create.contains(VBOType::IndexVert)) {
buffers.vbos.add_new(VBOType::IndexVert, extract_vert_index_subdiv(subdiv_cache, mr));
}
if (vbos_to_create.contains(VBOType::IndexEdge)) {
buffers.vbos.add_new(VBOType::IndexEdge, extract_edge_index_subdiv(subdiv_cache, mr));
}
if (vbos_to_create.contains(VBOType::IndexFace)) {
buffers.vbos.add_new(VBOType::IndexFace, extract_face_index_subdiv(subdiv_cache, mr));
}
if (vbos_to_create.contains(VBOType::VertexGroupWeight)) {
buffers.vbos.add_new(VBOType::VertexGroupWeight,
extract_weights_subdiv(mr, subdiv_cache, cache));
}
if (vbos_to_create.contains(VBOType::FaceDotNormal) ||
vbos_to_create.contains(VBOType::FaceDotPosition) ||
ibos_to_create.contains(IBOType::FaceDots))
{
gpu::VertBufPtr face_dot_position_vbo;
gpu::VertBufPtr face_dot_normal_vbo;
gpu::IndexBufPtr face_dot_ibo;
/* We use only one extractor for face dots, as the work is done in a single compute shader. */
extract_face_dots_subdiv(
subdiv_cache,
face_dot_position_vbo,
vbos_to_create.contains(VBOType::FaceDotNormal) ? &face_dot_normal_vbo : nullptr,
face_dot_ibo);
if (vbos_to_create.contains(VBOType::FaceDotPosition)) {
buffers.vbos.add_new(VBOType::FaceDotPosition, std::move(face_dot_position_vbo));
}
if (face_dot_normal_vbo) {
buffers.vbos.add_new(VBOType::FaceDotNormal, std::move(face_dot_normal_vbo));
}
if (ibos_to_create.contains(IBOType::FaceDots)) {
buffers.ibos.add_new(IBOType::FaceDots, std::move(face_dot_ibo));
}
}
if (vbos_to_create.contains(VBOType::PaintOverlayFlag)) {
buffers.vbos.add_new(VBOType::PaintOverlayFlag,
extract_paint_overlay_flags_subdiv(mr, subdiv_cache));
}
if (ibos_to_create.contains(IBOType::LinesPaintMask)) {
buffers.ibos.add_new(IBOType::LinesPaintMask,
extract_lines_paint_mask_subdiv(mr, subdiv_cache));
}
if (ibos_to_create.contains(IBOType::LinesAdjacency)) {
buffers.ibos.add_new(IBOType::LinesAdjacency,
extract_lines_adjacency_subdiv(subdiv_cache, cache.is_manifold));
}
if (vbos_to_create.contains(VBOType::SculptData)) {
buffers.vbos.add_new(VBOType::SculptData, extract_sculpt_data_subdiv(mr, subdiv_cache));
}
if (vbos_to_create.contains(VBOType::UVs)) {
/* Make sure UVs are computed before edituv stuffs. */
buffers.vbos.add_new(VBOType::UVs, extract_uv_maps_subdiv(subdiv_cache, cache));
}
if (ibos_to_create.contains(IBOType::AllUVLines)) {
buffers.ibos.add_new(IBOType::AllUVLines,
extract_edituv_lines_subdiv(mr, subdiv_cache, UvExtractionMode::All));
}
if (ibos_to_create.contains(IBOType::UVLines)) {
buffers.ibos.add_new(
IBOType::UVLines,
extract_edituv_lines_subdiv(mr, subdiv_cache, UvExtractionMode::Selection));
}
if (vbos_to_create.contains(VBOType::EditUVStretchArea)) {
buffers.vbos.add_new(
VBOType::EditUVStretchArea,
extract_edituv_stretch_area_subdiv(mr, subdiv_cache, cache.tot_area, cache.tot_uv_area));
}
if (vbos_to_create.contains(VBOType::EditUVStretchAngle)) {
buffers.vbos.add_new(VBOType::EditUVStretchAngle,
extract_edituv_stretch_angle_subdiv(mr, subdiv_cache, cache));
}
if (vbos_to_create.contains(VBOType::EditUVData)) {
buffers.vbos.add_new(VBOType::EditUVData, extract_edituv_data_subdiv(mr, subdiv_cache));
}
if (ibos_to_create.contains(IBOType::EditUVTris)) {
buffers.ibos.add_new(IBOType::EditUVTris, extract_edituv_tris_subdiv(mr, subdiv_cache));
}
if (ibos_to_create.contains(IBOType::EditUVLines)) {
buffers.ibos.add_new(IBOType::EditUVLines,
extract_edituv_lines_subdiv(mr, subdiv_cache, UvExtractionMode::Edit));
}
if (ibos_to_create.contains(IBOType::EditUVPoints)) {
buffers.ibos.add_new(IBOType::EditUVPoints, extract_edituv_points_subdiv(mr, subdiv_cache));
}
for (const int8_t i : IndexRange(GPU_MAX_ATTR)) {
const VBOType request = VBOType(int8_t(VBOType::Attr0) + i);
if (vbos_to_create.contains(request)) {
buffers.vbos.add_new(request,
extract_attribute_subdiv(mr, subdiv_cache, cache.attr_used[i]));
}
}
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,583 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Extraction of Mesh data into VBO to feed to GPU.
*/
#include "BLI_array.hh"
#include "BLI_array_utils.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_math_geom.h"
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
#include "BKE_attribute.hh"
#include "BKE_editmesh.hh"
#include "BKE_editmesh_cache.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "BKE_object_types.hh"
#include "ED_mesh.hh"
#include "DRW_render.hh"
#include "mesh_extractors/extract_mesh.hh"
/* ---------------------------------------------------------------------- */
/** \name Update Loose Geometry
* \{ */
namespace blender::draw {
static void mesh_render_data_loose_geom_build(const MeshRenderData &mr, MeshBufferCache &cache)
{
if (mr.extract_type == MeshExtractType::Mesh) {
cache.loose_geom.verts = mr.mesh->loose_verts();
cache.loose_geom.edges = mr.mesh->loose_edges();
}
else {
BMesh &bm = *mr.bm;
/* Use LinearAllocator instead of IndexMaskMemory to avoid over-allocation. */
cache.loose_geom.allocator = std::make_unique<LinearAllocator<>>();
cache.loose_geom.verts = IndexMask::from_predicate(
IndexRange(bm.totvert), *cache.loose_geom.allocator, [&](const int i) {
return BM_vert_at_index(&bm, i)->e == nullptr;
});
cache.loose_geom.edges = IndexMask::from_predicate(
IndexRange(bm.totedge), *cache.loose_geom.allocator, [&](const int i) {
return BM_edge_at_index(&bm, i)->l == nullptr;
});
}
}
static void mesh_render_data_loose_geom_ensure(const MeshRenderData &mr, MeshBufferCache &cache)
{
/* Early exit: Are loose geometry already available.
* Only checking for loose verts as loose edges and verts are calculated at the same time. */
if (!cache.loose_geom.verts.is_empty()) {
return;
}
mesh_render_data_loose_geom_build(mr, cache);
}
void mesh_render_data_update_loose_geom(MeshRenderData &mr, MeshBufferCache &cache)
{
mesh_render_data_loose_geom_ensure(mr, cache);
mr.loose_edges = cache.loose_geom.edges;
mr.loose_verts = cache.loose_geom.verts;
mr.loose_verts_num = cache.loose_geom.verts.size();
mr.loose_edges_num = cache.loose_geom.edges.size();
mr.loose_indices_num = mr.loose_verts_num + (mr.loose_edges_num * 2);
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Polygons sorted per material
*
* Contains face indices sorted based on their material.
* \{ */
static void accumululate_material_counts_bm(
const BMesh &bm, threading::EnumerableThreadSpecific<Array<int>> &all_tri_counts)
{
threading::parallel_for(IndexRange(bm.totface), 1024, [&](const IndexRange range) {
Array<int> &tri_counts = all_tri_counts.local();
const short last_index = tri_counts.size() - 1;
for (const int i : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), i);
if (!BM_elem_flag_test(&face, BM_ELEM_HIDDEN)) {
const short mat = std::clamp<short>(face.mat_nr, 0, last_index);
tri_counts[mat] += face.len - 2;
}
}
});
}
static void accumululate_material_counts_mesh(
const MeshRenderData &mr, threading::EnumerableThreadSpecific<Array<int>> &all_tri_counts)
{
const OffsetIndices faces = mr.faces;
const Span<bool> hide_poly = mr.hide_poly;
const Span material_indices = mr.material_indices;
if (material_indices.is_empty()) {
if (!hide_poly.is_empty()) {
all_tri_counts.local().first() = threading::parallel_reduce(
faces.index_range(),
4096,
0,
[&](const IndexRange range, int count) {
for (const int face : range) {
if (!hide_poly[face]) {
count += bke::mesh::face_triangles_num(faces[face].size());
}
}
return count;
},
std::plus<>());
}
else {
all_tri_counts.local().first() = poly_to_tri_count(mr.faces_num, mr.corners_num);
}
return;
}
threading::parallel_for(material_indices.index_range(), 1024, [&](const IndexRange range) {
Array<int> &tri_counts = all_tri_counts.local();
const int last_index = tri_counts.size() - 1;
if (!hide_poly.is_empty()) {
for (const int i : range) {
if (!hide_poly[i]) {
const int mat = std::clamp(material_indices[i], 0, last_index);
tri_counts[mat] += bke::mesh::face_triangles_num(faces[i].size());
}
}
}
else {
for (const int i : range) {
const int mat = std::clamp(material_indices[i], 0, last_index);
tri_counts[mat] += bke::mesh::face_triangles_num(faces[i].size());
}
}
});
}
/* Count how many triangles for each material. */
static Array<int> mesh_render_data_mat_tri_len_build(const MeshRenderData &mr)
{
threading::EnumerableThreadSpecific<Array<int>> all_tri_counts(
[&]() { return Array<int>(mr.materials_num, 0); });
if (mr.extract_type == MeshExtractType::BMesh) {
accumululate_material_counts_bm(*mr.bm, all_tri_counts);
}
else {
accumululate_material_counts_mesh(mr, all_tri_counts);
}
Array<int> &tris_num_by_material = all_tri_counts.local();
for (const Array<int> &counts : all_tri_counts) {
if (&counts != &tris_num_by_material) {
for (const int i : tris_num_by_material.index_range()) {
tris_num_by_material[i] += counts[i];
}
}
}
return std::move(tris_num_by_material);
}
static Array<int> calc_face_tri_starts_bmesh(const MeshRenderData &mr,
MutableSpan<int> material_tri_starts)
{
BMesh &bm = *mr.bm;
Array<int> face_tri_offsets(bm.totface);
#ifndef NDEBUG
face_tri_offsets.fill(-1);
#endif
const int mat_last = mr.materials_num - 1;
BMIter iter;
BMFace *face;
int i;
BM_ITER_MESH_INDEX (face, &iter, &bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(face, BM_ELEM_HIDDEN)) {
continue;
}
const int mat = std::clamp(int(face->mat_nr), 0, mat_last);
face_tri_offsets[i] = material_tri_starts[mat];
material_tri_starts[mat] += face->len - 2;
}
return face_tri_offsets;
}
static bool mesh_is_single_material(const OffsetIndices<int> material_tri_starts)
{
const int used_materials = std::count_if(
material_tri_starts.index_range().begin(),
material_tri_starts.index_range().end(),
[&](const int i) { return material_tri_starts[i].size() > 0; });
return used_materials == 1;
}
static std::optional<Array<int>> calc_face_tri_starts_mesh(const MeshRenderData &mr,
MutableSpan<int> material_tri_starts)
{
const bool single_material = mesh_is_single_material(material_tri_starts.as_span());
if (single_material && mr.hide_poly.is_empty()) {
return std::nullopt;
}
const OffsetIndices faces = mr.faces;
const Span<bool> hide_poly = mr.hide_poly;
Array<int> face_tri_offsets(faces.size());
#ifndef NDEBUG
face_tri_offsets.fill(-1);
#endif
if (single_material) {
int offset = 0;
for (const int face : faces.index_range()) {
if (hide_poly[face]) {
continue;
}
face_tri_offsets[face] = offset;
offset += bke::mesh::face_triangles_num(faces[face].size());
}
return face_tri_offsets;
}
const Span<int> material_indices = mr.material_indices;
const int mat_last = mr.materials_num - 1;
for (const int face : faces.index_range()) {
if (!hide_poly.is_empty() && hide_poly[face]) {
continue;
}
const int mat = std::clamp(material_indices[face], 0, mat_last);
face_tri_offsets[face] = material_tri_starts[mat];
material_tri_starts[mat] += bke::mesh::face_triangles_num(faces[face].size());
}
return face_tri_offsets;
}
static SortedFaceData mesh_render_data_faces_sorted_build(const MeshRenderData &mr)
{
SortedFaceData cache;
cache.tris_num_by_material = mesh_render_data_mat_tri_len_build(mr);
const Span<int> tris_num_by_material = cache.tris_num_by_material;
Array<int, 32> material_tri_starts(mr.materials_num + 1);
material_tri_starts.as_mutable_span().drop_back(1).copy_from(tris_num_by_material);
offset_indices::accumulate_counts_to_offsets(material_tri_starts);
cache.visible_tris_num = material_tri_starts.last();
/* Sort per material. */
if (mr.extract_type == MeshExtractType::BMesh) {
cache.face_tri_offsets = calc_face_tri_starts_bmesh(mr, material_tri_starts);
}
else {
cache.face_tri_offsets = calc_face_tri_starts_mesh(mr, material_tri_starts);
}
return cache;
}
const SortedFaceData &mesh_render_data_faces_sorted_ensure(const MeshRenderData &mr,
MeshBufferCache &cache)
{
if (cache.face_sorted.visible_tris_num == 0) {
cache.face_sorted = mesh_render_data_faces_sorted_build(mr);
}
return cache.face_sorted;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Mesh/BMesh Interface (indirect, partially cached access to complex data).
* \{ */
static bool bm_edge_is_sharp(const BMEdge *const &edge)
{
return !BM_elem_flag_test(edge, BM_ELEM_SMOOTH);
}
static bool bm_face_is_sharp(const BMFace *const &face)
{
return !BM_elem_flag_test(face, BM_ELEM_SMOOTH);
}
/**
* Returns which domain of normals is required because of sharp and smooth flags.
* Similar to #Mesh::normals_domain().
*/
static bke::MeshNormalDomain bmesh_normals_domain(BMesh *bm)
{
if (bm->totface == 0) {
return bke::MeshNormalDomain::Point;
}
if (CustomData_has_layer_named(&bm->vdata, CD_PROP_FLOAT3, "custom_normal")) {
return bke::MeshNormalDomain::Point;
}
if (CustomData_has_layer_named(&bm->pdata, CD_PROP_FLOAT3, "custom_normal")) {
return bke::MeshNormalDomain::Face;
}
if (CustomData_has_layer_named(&bm->ldata, CD_PROP_FLOAT3, "custom_normal")) {
return bke::MeshNormalDomain::Corner;
}
if (CustomData_has_layer_named(&bm->ldata, CD_PROP_INT16_2D, "custom_normal")) {
return bke::MeshNormalDomain::Corner;
}
BM_mesh_elem_table_ensure(bm, BM_FACE);
const VArray<bool> sharp_faces =
VArray<bool>::from_derived_span<const BMFace *, bm_face_is_sharp>(
Span(bm->ftable, bm->totface));
const array_utils::BooleanMix face_mix = array_utils::booleans_mix_calc(sharp_faces);
if (face_mix == array_utils::BooleanMix::AllTrue) {
return bke::MeshNormalDomain::Face;
}
BM_mesh_elem_table_ensure(bm, BM_EDGE);
const VArray<bool> sharp_edges =
VArray<bool>::from_derived_span<const BMEdge *, bm_edge_is_sharp>(
Span(bm->etable, bm->totedge));
const array_utils::BooleanMix edge_mix = array_utils::booleans_mix_calc(sharp_edges);
if (edge_mix == array_utils::BooleanMix::AllTrue) {
return bke::MeshNormalDomain::Face;
}
if (edge_mix == array_utils::BooleanMix::AllFalse &&
face_mix == array_utils::BooleanMix::AllFalse)
{
return bke::MeshNormalDomain::Point;
}
return bke::MeshNormalDomain::Corner;
}
void mesh_render_data_update_corner_normals(MeshRenderData &mr)
{
if (mr.extract_type == MeshExtractType::Mesh) {
mr.corner_normals = mr.mesh->corner_normals();
}
else {
if (mr.bm_free_normal_offset_vert != -1 || mr.bm_free_normal_offset_face != -1 ||
mr.bm_free_normal_offset_corner != -1)
{
/* If there are free custom normals they should be used directly. */
mr.bm_loop_normals = {};
return;
}
mr.bm_loop_normals.reinitialize(mr.corners_num);
const int clnors_offset = CustomData_get_offset_named(
&mr.bm->ldata, CD_PROP_INT16_2D, "custom_normal");
BM_loops_calc_normal_vcos(mr.bm,
mr.bm_vert_coords,
mr.bm_vert_normals,
mr.bm_face_normals,
true,
mr.bm_loop_normals,
nullptr,
nullptr,
clnors_offset,
false);
}
}
void mesh_render_data_update_face_normals(MeshRenderData &mr)
{
if (mr.extract_type == MeshExtractType::Mesh) {
/* Eager calculation of face normals can reduce waiting on the lazy cache's lock. */
mr.face_normals = mr.mesh->face_normals();
}
else {
/* Use #BMFace.no. */
}
}
static void retrieve_active_attribute_names(MeshRenderData &mr,
const Object &object,
const Mesh &mesh)
{
const Mesh &mesh_final = editmesh_final_or_this(object, mesh);
mr.active_color_name = mesh_final.active_color_attribute ? mesh_final.active_color_attribute :
mesh_final.default_color_attribute;
mr.default_color_name = mesh_final.default_color_attribute;
}
static BMEditMesh *mesh_get_original_edit_mesh(const Object &object)
{
BLI_assert(object.type == OB_MESH);
if (const ID *data_orig = object.runtime->data_orig) {
if (GS(data_orig->name) == blender::ID_ME) {
return id_cast<const Mesh *>(data_orig)->runtime->edit_mesh.get();
}
}
return nullptr;
}
MeshRenderData mesh_render_data_create(Object &object,
Mesh &mesh,
const bool is_editmode,
const bool is_paint_mode,
const bool do_final,
const bool do_uvedit,
const bool use_hide,
const ToolSettings *ts)
{
MeshRenderData mr{};
mr.toolsettings = ts;
mr.materials_num = BKE_object_material_used_with_fallback_eval(object);
mr.use_hide = use_hide;
if (BMEditMesh *edit_mesh = mesh_get_original_edit_mesh(object)) {
const Mesh *eval_cage = DRW_object_get_editmesh_cage_for_drawing(object);
mr.bm = edit_mesh->bm;
mr.edit_bmesh = edit_mesh;
mr.mesh = (do_final) ? &mesh : eval_cage;
mr.edit_data = is_editmode ? mr.mesh->runtime->edit_data.get() : nullptr;
/* If there is no distinct cage, hide unmapped edges that can't be selected. */
mr.hide_unmapped_edges = !do_final || &mesh == eval_cage;
mr.bm_free_normal_offset_vert = CustomData_get_offset_named(
&mr.bm->vdata, CD_PROP_FLOAT3, "custom_normal");
mr.bm_free_normal_offset_face = CustomData_get_offset_named(
&mr.bm->pdata, CD_PROP_FLOAT3, "custom_normal");
mr.bm_free_normal_offset_corner = CustomData_get_offset_named(
&mr.bm->ldata, CD_PROP_FLOAT3, "custom_normal");
if (bke::EditMeshData *emd = mr.edit_data) {
if (!emd->vert_positions.is_empty()) {
mr.bm_vert_coords = mr.edit_data->vert_positions;
if (mr.bm_free_normal_offset_vert == -1) {
mr.bm_vert_normals = BKE_editmesh_cache_ensure_vert_normals(*mr.edit_bmesh, *emd);
}
if (mr.bm_free_normal_offset_face == -1) {
mr.bm_face_normals = BKE_editmesh_cache_ensure_face_normals(*mr.edit_bmesh, *emd);
}
}
}
int bm_ensure_types = BM_VERT | BM_EDGE | BM_LOOP | BM_FACE;
BM_mesh_elem_index_ensure(mr.bm, bm_ensure_types);
BM_mesh_elem_table_ensure(mr.bm, bm_ensure_types & ~BM_LOOP);
mr.efa_act_uv = EDBM_uv_active_face_get(mr.edit_bmesh, false, false);
mr.efa_act = BM_mesh_active_face_get(mr.bm, false, true);
mr.eed_act = BM_mesh_active_edge_get(mr.bm);
mr.eve_act = BM_mesh_active_vert_get(mr.bm);
mr.vert_crease_ofs = CustomData_get_offset_named(&mr.bm->vdata, CD_PROP_FLOAT, "crease_vert");
mr.edge_crease_ofs = CustomData_get_offset_named(&mr.bm->edata, CD_PROP_FLOAT, "crease_edge");
mr.bweight_ofs = CustomData_get_offset_named(
&mr.bm->edata, CD_PROP_FLOAT, "bevel_weight_edge");
#ifdef WITH_FREESTYLE
mr.freestyle_edge_ofs = CustomData_get_offset_named(
&mr.bm->edata, CD_PROP_BOOL, "freestyle_edge");
mr.freestyle_face_ofs = CustomData_get_offset_named(
&mr.bm->pdata, CD_PROP_BOOL, "freestyle_face");
#endif
/* Use bmesh directly when the object is unchanged by any modifiers. For non-final UVs, always
* use original bmesh since the UV editor does not support using the cage mesh with deformed
* coordinates. */
if ((mr.mesh->runtime->is_original_bmesh &&
mr.mesh->runtime->wrapper_type == ME_WRAPPER_TYPE_BMESH) ||
(do_uvedit && !do_final))
{
mr.extract_type = MeshExtractType::BMesh;
}
else {
mr.extract_type = MeshExtractType::Mesh;
/* Use mapping from final to original mesh when the object is in edit mode. */
if (is_editmode && do_final) {
mr.orig_index_vert = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->vert_data, CD_ORIGINDEX));
mr.orig_index_edge = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->edge_data, CD_ORIGINDEX));
mr.orig_index_face = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->face_data, CD_ORIGINDEX));
}
else {
mr.orig_index_vert = nullptr;
mr.orig_index_edge = nullptr;
mr.orig_index_face = nullptr;
}
}
}
else {
mr.mesh = &mesh;
mr.edit_bmesh = nullptr;
mr.extract_type = MeshExtractType::Mesh;
mr.hide_unmapped_edges = false;
if (is_paint_mode && mr.mesh) {
mr.orig_index_vert = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->vert_data, CD_ORIGINDEX));
mr.orig_index_edge = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->edge_data, CD_ORIGINDEX));
mr.orig_index_face = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->face_data, CD_ORIGINDEX));
}
else {
mr.orig_index_vert = nullptr;
mr.orig_index_edge = nullptr;
mr.orig_index_face = nullptr;
}
}
if (mr.extract_type == MeshExtractType::Mesh) {
mr.verts_num = mr.mesh->verts_num;
mr.edges_num = mr.mesh->edges_num;
mr.faces_num = mr.mesh->faces_num;
mr.corners_num = mr.mesh->corners_num;
mr.corner_tris_num = poly_to_tri_count(mr.faces_num, mr.corners_num);
mr.vert_positions = mr.mesh->vert_positions();
mr.edges = mr.mesh->edges();
mr.faces = mr.mesh->faces();
mr.corner_verts = mr.mesh->corner_verts();
mr.corner_edges = mr.mesh->corner_edges();
mr.orig_index_vert = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->vert_data, CD_ORIGINDEX));
mr.orig_index_edge = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->edge_data, CD_ORIGINDEX));
mr.orig_index_face = static_cast<const int *>(
CustomData_get_layer(&mr.mesh->face_data, CD_ORIGINDEX));
mr.normals_domain = mr.mesh->normals_domain();
const bke::AttributeAccessor attributes = mr.mesh->attributes();
mr.material_indices = *attributes.lookup<int>("material_index", bke::AttrDomain::Face);
if (is_editmode || is_paint_mode) {
if (use_hide) {
mr.hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
mr.hide_edge = *attributes.lookup<bool>(".hide_edge", bke::AttrDomain::Edge);
mr.hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
}
mr.select_vert = *attributes.lookup<bool>(".select_vert", bke::AttrDomain::Point);
mr.select_edge = *attributes.lookup<bool>(".select_edge", bke::AttrDomain::Edge);
mr.select_poly = *attributes.lookup<bool>(".select_poly", bke::AttrDomain::Face);
}
mr.sharp_faces = *attributes.lookup<bool>("sharp_face", bke::AttrDomain::Face);
}
else {
BMesh *bm = mr.bm;
mr.verts_num = bm->totvert;
mr.edges_num = bm->totedge;
mr.faces_num = bm->totface;
mr.corners_num = bm->totloop;
mr.corner_tris_num = poly_to_tri_count(mr.faces_num, mr.corners_num);
mr.normals_domain = bmesh_normals_domain(bm);
}
retrieve_active_attribute_names(mr, object, *mr.mesh);
return mr;
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,342 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include <cstdint>
#include "BLI_math_matrix_types.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "GPU_vertex_buffer.hh"
namespace blender {
struct GPUMaterial;
namespace draw {
struct MeshBatchCache;
};
namespace gpu {
class Batch;
class UniformBuf;
class VertBuf;
} // namespace gpu
struct ModifierData;
struct PTCacheEdit;
struct ParticleSystem;
struct TaskGraph;
struct Curve;
struct Curves;
struct Lattice;
struct Mesh;
struct Object;
struct Scene;
struct PointCloud;
struct Volume;
struct GreasePencil;
enum eMeshBatchDirtyMode : int8_t;
namespace draw {
class ObjectRef;
/* -------------------------------------------------------------------- */
/** \name Expose via BKE callbacks
* \{ */
void DRW_curve_batch_cache_dirty_tag(Curve *cu, int mode);
void DRW_curve_batch_cache_validate(Curve *cu);
void DRW_curve_batch_cache_free(Curve *cu);
void DRW_mesh_batch_cache_dirty_tag(Mesh *mesh, eMeshBatchDirtyMode mode);
void DRW_mesh_batch_cache_validate(Mesh &mesh);
void DRW_mesh_batch_cache_free(draw::MeshBatchCache *batch_cache);
void DRW_lattice_batch_cache_dirty_tag(Lattice *lt, int mode);
void DRW_lattice_batch_cache_validate(Lattice *lt);
void DRW_lattice_batch_cache_free(Lattice *lt);
void DRW_particle_batch_cache_dirty_tag(ParticleSystem *psys, int mode);
void DRW_particle_batch_cache_free(ParticleSystem *psys);
void DRW_curves_batch_cache_dirty_tag(Curves *curves, int mode);
void DRW_curves_batch_cache_validate(Curves *curves);
void DRW_curves_batch_cache_free(Curves *curves);
void DRW_pointcloud_batch_cache_dirty_tag(PointCloud *pointcloud, int mode);
void DRW_pointcloud_batch_cache_validate(PointCloud *pointcloud);
void DRW_pointcloud_batch_cache_free(PointCloud *pointcloud);
void DRW_volume_batch_cache_dirty_tag(Volume *volume, int mode);
void DRW_volume_batch_cache_validate(Volume *volume);
void DRW_volume_batch_cache_free(Volume *volume);
void DRW_grease_pencil_batch_cache_dirty_tag(GreasePencil *grease_pencil, int mode);
void DRW_grease_pencil_batch_cache_validate(GreasePencil *grease_pencil);
void DRW_grease_pencil_batch_cache_free(GreasePencil *grease_pencil);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Garbage Collection
* \{ */
void DRW_batch_cache_free_old(Object *ob, int ctime);
/**
* Thread safety need to be assured by caller. Don't call this during drawing.
* \note For now this only free the shading batches / VBO if any cd layers is not needed anymore.
*/
void DRW_mesh_batch_cache_free_old(Mesh *mesh, int ctime);
void DRW_curves_batch_cache_free_old(Curves *curves, int ctime);
void DRW_pointcloud_batch_cache_free_old(PointCloud *pointcloud, int ctime);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Generic
* \{ */
void DRW_vertbuf_create_wiredata(gpu::VertBuf *vbo, int vert_len);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Curve
* \{ */
void DRW_curve_batch_cache_create_requested(Object *ob, const Scene *scene);
gpu::Batch *DRW_curve_batch_cache_get_wire_edge(Curve *cu);
gpu::Batch *DRW_curve_batch_cache_get_wire_edge_viewer_attribute(Curve *cu);
gpu::Batch *DRW_curve_batch_cache_get_normal_edge(Curve *cu);
gpu::Batch *DRW_curve_batch_cache_get_edit_edges(Curve *cu);
gpu::Batch *DRW_curve_batch_cache_get_edit_verts(Curve *cu);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lattice
* \{ */
gpu::Batch *DRW_lattice_batch_cache_get_all_edges(Lattice *lt, bool use_weight, int actdef);
gpu::Batch *DRW_lattice_batch_cache_get_all_verts(Lattice *lt);
gpu::Batch *DRW_lattice_batch_cache_get_edit_verts(Lattice *lt);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Curves
* \{ */
/**
* Provide GPU access to a specific evaluated attribute on curves.
*
* \return A pointer to location where the texture will be
* stored, which will be filled by #DRW_shgroup_curves_create_sub.
*/
gpu::VertBufPtr &DRW_curves_texture_for_evaluated_attribute(Curves *curves,
StringRef name,
bool &r_is_point_domain,
bool &r_valid_attribute);
gpu::Batch *DRW_curves_batch_cache_get_edit_points(Curves *curves);
gpu::Batch *DRW_curves_batch_cache_get_sculpt_curves_cage(Curves *curves);
gpu::Batch *DRW_curves_batch_cache_get_edit_curves_handles(Curves *curves);
gpu::Batch *DRW_curves_batch_cache_get_edit_curves_lines(Curves *curves);
void DRW_curves_batch_cache_create_requested(Object *ob);
/** \} */
/* -------------------------------------------------------------------- */
/** \name PointCloud
* \{ */
gpu::VertBuf *DRW_pointcloud_position_and_radius_buffer_get(Object *ob);
gpu::VertBuf **DRW_pointcloud_evaluated_attribute(PointCloud *pointcloud, StringRef name);
gpu::Batch *DRW_pointcloud_batch_cache_get_dots(Object *ob);
gpu::Batch *DRW_pointcloud_batch_cache_get_edit_dots(PointCloud *pointcloud);
void DRW_pointcloud_batch_cache_create_requested(Object *ob);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Volume
* \{ */
gpu::Batch *DRW_volume_batch_cache_get_wireframes_face(Volume *volume);
gpu::Batch *DRW_volume_batch_cache_get_selection_surface(Volume *volume);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mesh
* \{ */
/**
* Can be called for any surface type. Mesh *mesh is the final mesh.
*/
void DRW_mesh_batch_cache_create_requested(TaskGraph &task_graph,
Object &ob,
Mesh &mesh,
const Scene &scene,
bool is_paint_mode,
bool use_hide);
gpu::Batch *DRW_mesh_batch_cache_get_all_verts(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_paint_overlay_verts(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_all_edges(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_loose_edges(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edge_detection(Mesh &mesh, bool *r_is_manifold);
gpu::Batch *DRW_mesh_batch_cache_get_surface(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_paint_overlay_surface(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_paint_overlay_edges(Mesh &mesh);
Span<gpu::Batch *> DRW_mesh_batch_cache_get_surface_shaded(Object &object,
Mesh &mesh,
Span<const GPUMaterial *> materials);
Span<gpu::Batch *> DRW_mesh_batch_cache_get_surface_texpaint(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_surface_texpaint_single(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_surface_vertpaint(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_surface_sculpt(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_surface_weights(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_sculpt_overlays(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_surface_viewer_attribute(Mesh &mesh);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edit-Mesh Drawing
* \{ */
gpu::Batch *DRW_mesh_batch_cache_get_edit_triangles(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_vertices(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_edges(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_vert_normals(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_loop_normals(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_facedots(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_skin_roots(Mesh &mesh);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edit-mesh Selection
* \{ */
gpu::Batch *DRW_mesh_batch_cache_get_triangles_with_select_id(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_facedots_with_select_id(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edges_with_select_id(Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_verts_with_select_id(Mesh &mesh);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Object Mode Wireframe Overlays
* \{ */
gpu::Batch *DRW_mesh_batch_cache_get_wireframes_face(Mesh &mesh);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edit-mesh UV Editor
* \{ */
/**
* Creates the #gpu::Batch for drawing the UV Stretching Area Overlay.
* Optional retrieves the total area or total uv area of the mesh.
*
* The `cache->tot_area` and `cache->tot_uv_area` update are calculation are
* only valid after calling `DRW_mesh_batch_cache_create_requested`.
*/
gpu::Batch *DRW_mesh_batch_cache_get_edituv_faces_stretch_area(Object &object,
Mesh &mesh,
float **tot_area,
float **tot_uv_area);
gpu::Batch *DRW_mesh_batch_cache_get_edituv_faces_stretch_angle(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edituv_faces(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edituv_wireframe(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edituv_edges(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edituv_verts(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edituv_facedots(Object &object, Mesh &mesh);
/** \} */
/* -------------------------------------------------------------------- */
/** \name For Image UV Editor
* \{ */
gpu::Batch *DRW_mesh_batch_cache_get_uv_faces(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_all_uv_wireframe(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_uv_wireframe(Object &object, Mesh &mesh);
gpu::Batch *DRW_mesh_batch_cache_get_edit_mesh_analysis(Mesh &mesh);
/** \} */
/* -------------------------------------------------------------------- */
/** \name For Direct Data Access
* \{ */
/* Edit mesh bit-flags (is this the right place?). */
enum {
VFLAG_VERT_ACTIVE = 1 << 0,
VFLAG_VERT_SELECTED = 1 << 1,
VFLAG_VERT_SELECTED_BEZT_HANDLE = 1 << 2,
VFLAG_EDGE_ACTIVE = 1 << 3,
VFLAG_EDGE_SELECTED = 1 << 4,
VFLAG_EDGE_SEAM = 1 << 5,
VFLAG_EDGE_SHARP = 1 << 6,
VFLAG_EDGE_FREESTYLE = 1 << 7,
/* Beware to not go over 1 << 7 (it's a byte flag). */
/* NOTE: Grease pencil edit curve use another type of data format that allows for this value. */
VFLAG_VERT_GPENCIL_BEZT_HANDLE = 1 << 30,
};
enum {
VFLAG_FACE_ACTIVE = 1 << 0,
VFLAG_FACE_SELECTED = 1 << 1,
VFLAG_FACE_FREESTYLE = 1 << 2,
VFLAG_VERT_UV_SELECT = 1 << 3,
VFLAG_VERT_UV_PINNED = 1 << 4,
VFLAG_EDGE_UV_SELECT = 1 << 5,
VFLAG_FACE_UV_ACTIVE = 1 << 6,
VFLAG_FACE_UV_SELECT = 1 << 7,
/* Beware to not go over 1 << 7 (it's a byte flag). */
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Particles
* \{ */
gpu::Batch *DRW_particles_batch_cache_get_hair(Object *object,
ParticleSystem *psys,
ModifierData *md);
gpu::Batch *DRW_particles_batch_cache_get_dots(Object *object, ParticleSystem *psys);
gpu::Batch *DRW_particles_batch_cache_get_edit_strands(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit,
bool use_weight);
gpu::Batch *DRW_particles_batch_cache_get_edit_inner_points(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit);
gpu::Batch *DRW_particles_batch_cache_get_edit_tip_points(Object *object,
ParticleSystem *psys,
PTCacheEdit *edit);
/** \} */
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,932 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Curve API for render engines
*/
#include "MEM_guardedalloc.h"
#include "BLI_color_types.hh"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
#include "DNA_curve_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_curve.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_object_types.hh"
#include "BKE_vfont.hh"
#include "GPU_attribute_convert.hh"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "GPU_texture.hh"
#include "UI_resources.hh"
#include "DRW_render.hh"
#include "draw_cache_inline.hh"
#include "draw_cache_impl.hh" /* own include */
namespace blender::draw {
/* See: edit_curve_point_vert.glsl for duplicate includes. */
#define SELECT 1
#define ACTIVE_NURB (1 << 2)
#define BEZIER_HANDLE (1 << 3)
#define EVEN_U_BIT (1 << 4) /* Alternate this bit for every U vert. */
#define COLOR_SHIFT 5
/* Used as values of `color_id` in `edit_curve_overlay_handle_geom.glsl` */
enum {
COLOR_NURB_ULINE_ID = TH_HANDLE_AUTOCLAMP - TH_HANDLE_FREE + 2,
TOT_HANDLE_COL,
};
/**
* TODO
* - Ensure `CurveCache`, `SEQUENCER_DAG_WORKAROUND`.
* - Check number of verts/edges to see if cache is valid.
* - Check if 'overlay.edges' can use single attribute per edge, not 2 (for selection drawing).
*/
static void curve_batch_cache_clear(Curve *cu);
/* ---------------------------------------------------------------------- */
/* Curve Interface, direct access to basic data. */
static void curve_render_overlay_verts_edges_len_get(ListBaseT<Nurb> *lb,
int *r_vert_len,
int *r_edge_len)
{
BLI_assert(r_vert_len || r_edge_len);
int vert_len = 0;
int edge_len = 0;
for (Nurb &nu : *lb) {
if (nu.bezt) {
vert_len += nu.pntsu * 3;
/* 2x handles per point. */
edge_len += 2 * nu.pntsu;
}
else if (nu.bp) {
vert_len += nu.pntsu * nu.pntsv;
/* segments between points */
edge_len += (nu.pntsu - 1) * nu.pntsv;
edge_len += (nu.pntsv - 1) * nu.pntsu;
}
}
if (r_vert_len) {
*r_vert_len = vert_len;
}
if (r_edge_len) {
*r_edge_len = edge_len;
}
}
static void curve_eval_render_wire_verts_edges_len_get(const bke::CurvesGeometry &curves,
int *r_curve_len,
int *r_vert_len,
int *r_edge_len)
{
const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
*r_curve_len = curves.curves_num();
*r_vert_len = points_by_curve.total_size();
*r_edge_len = 0;
for (const int i : curves.curves_range()) {
*r_edge_len += bke::curves::segments_num(points_by_curve[i].size(), cyclic[i]);
}
}
static int curve_render_normal_len_get(const ListBaseT<Nurb> *lb, const CurveCache *ob_curve_cache)
{
int normal_len = 0;
const BevList *bl;
const Nurb *nu;
for (bl = static_cast<const BevList *>(ob_curve_cache->bev.first),
nu = static_cast<const Nurb *>(lb->first);
nu && bl;
bl = bl->next, nu = nu->next)
{
int nr = bl->nr;
int skip = nu->resolu / 16;
#if 0
while (nr-- > 0) { /* accounts for empty bevel lists */
normal_len += 1;
nr -= skip;
}
#else
/* Same as loop above */
normal_len += (nr / (skip + 1)) + ((nr % (skip + 1)) != 0);
#endif
}
return normal_len;
}
/* ---------------------------------------------------------------------- */
/* Curve Interface, indirect, partially cached access to complex data. */
struct CurveRenderData {
int types;
struct {
int vert_len;
int edge_len;
} overlay;
struct {
int curve_len;
int vert_len;
int edge_len;
} wire;
/* edit mode normal's */
struct {
/* 'edge_len == len * 2'
* 'vert_len == len * 3' */
int len;
} normal;
struct {
EditFont *edit_font;
} text;
/* borrow from 'Object' */
CurveCache *ob_curve_cache;
/* Owned by the evaluated object's geometry set (#geometry_set_eval). */
const Curves *curve_eval;
/* borrow from 'Curve' */
ListBaseT<Nurb> *nurbs;
/* edit, index in nurb list */
int actnu;
/* edit, index in active nurb (BPoint or BezTriple) */
int actvert;
};
enum {
/* Wire center-line */
CU_DATATYPE_WIRE = 1 << 0,
/* Edit-mode verts and optionally handles */
CU_DATATYPE_OVERLAY = 1 << 1,
/* Edit-mode normals */
CU_DATATYPE_NORMAL = 1 << 2,
/* Geometry */
CU_DATATYPE_SURFACE = 1 << 3,
/* Text */
CU_DATATYPE_TEXT_SELECT = 1 << 4,
};
/**
* \param ob_curve_cache: can be null.
*/
static CurveRenderData *curve_render_data_create(Curve *cu,
CurveCache *ob_curve_cache,
const int types)
{
CurveRenderData *rdata = MEM_new_zeroed<CurveRenderData>(__func__);
rdata->types = types;
ListBaseT<Nurb> *nurbs;
rdata->actnu = cu->actnu;
rdata->actvert = cu->actvert;
rdata->ob_curve_cache = ob_curve_cache;
rdata->curve_eval = cu->curve_eval;
if (types & CU_DATATYPE_WIRE) {
if (rdata->curve_eval != nullptr) {
curve_eval_render_wire_verts_edges_len_get(rdata->curve_eval->geometry.wrap(),
&rdata->wire.curve_len,
&rdata->wire.vert_len,
&rdata->wire.edge_len);
}
}
if (cu->editnurb) {
EditNurb *editnurb = cu->editnurb;
nurbs = &editnurb->nurbs;
if (types & CU_DATATYPE_OVERLAY) {
curve_render_overlay_verts_edges_len_get(
nurbs, &rdata->overlay.vert_len, &rdata->overlay.edge_len);
rdata->actnu = cu->actnu;
rdata->actvert = cu->actvert;
}
if (types & CU_DATATYPE_NORMAL) {
rdata->normal.len = curve_render_normal_len_get(nurbs, rdata->ob_curve_cache);
}
}
else {
nurbs = &cu->nurb;
}
rdata->nurbs = nurbs;
rdata->text.edit_font = cu->editfont;
return rdata;
}
static void curve_render_data_free(CurveRenderData *rdata)
{
#if 0
if (rdata->loose_verts) {
MEM_delete(rdata->loose_verts);
}
#endif
MEM_delete(rdata);
}
static int curve_render_data_overlay_verts_len_get(const CurveRenderData *rdata)
{
BLI_assert(rdata->types & CU_DATATYPE_OVERLAY);
return rdata->overlay.vert_len;
}
static int curve_render_data_overlay_edges_len_get(const CurveRenderData *rdata)
{
BLI_assert(rdata->types & CU_DATATYPE_OVERLAY);
return rdata->overlay.edge_len;
}
static int curve_render_data_wire_verts_len_get(const CurveRenderData *rdata)
{
BLI_assert(rdata->types & CU_DATATYPE_WIRE);
return rdata->wire.vert_len;
}
static int curve_render_data_wire_edges_len_get(const CurveRenderData *rdata)
{
BLI_assert(rdata->types & CU_DATATYPE_WIRE);
return rdata->wire.edge_len;
}
static int curve_render_data_wire_curve_len_get(const CurveRenderData *rdata)
{
BLI_assert(rdata->types & CU_DATATYPE_WIRE);
return rdata->wire.curve_len;
}
static int curve_render_data_normal_len_get(const CurveRenderData *rdata)
{
BLI_assert(rdata->types & CU_DATATYPE_NORMAL);
return rdata->normal.len;
}
/* ---------------------------------------------------------------------- */
/* Curve gpu::Batch Cache */
struct CurveBatchCache {
struct {
gpu::VertBuf *curves_pos;
gpu::VertBuf *attr_viewer;
} ordered;
struct {
gpu::VertBuf *curves_nor;
/* Edit points (beztriples and bpoints) */
gpu::VertBuf *pos;
gpu::VertBuf *data;
} edit;
struct {
gpu::IndexBuf *curves_lines;
/* Edit mode */
gpu::IndexBuf *edit_verts;
gpu::IndexBuf *edit_lines;
} ibo;
struct {
gpu::Batch *curves;
gpu::Batch *curves_viewer_attribute;
/* control handles and vertices */
gpu::Batch *edit_edges;
gpu::Batch *edit_verts;
gpu::Batch *edit_normals;
} batch;
/* settings to determine if cache is invalid */
bool is_dirty;
bool is_editmode;
};
/* gpu::Batch cache management. */
static bool curve_batch_cache_valid(Curve *cu)
{
CurveBatchCache *cache = cu->batch_cache;
if (cache == nullptr) {
return false;
}
if (cache->is_dirty) {
return false;
}
if (cache->is_editmode != ((cu->editnurb != nullptr) || (cu->editfont != nullptr))) {
return false;
}
if (cache->is_editmode) {
if (cu->editfont) {
/* TODO */
}
}
return true;
}
static void curve_batch_cache_init(Curve *cu)
{
CurveBatchCache *cache = cu->batch_cache;
if (!cache) {
cache = MEM_new_zeroed<CurveBatchCache>(__func__);
cu->batch_cache = cache;
}
else {
memset(cache, 0, sizeof(*cache));
}
#if 0
ListBaseT<Nurb> *nurbs;
if (cu->editnurb) {
EditNurb *editnurb = cu->editnurb;
nurbs = &editnurb->nurbs;
}
else {
nurbs = &cu->nurb;
}
#endif
cache->is_editmode = (cu->editnurb != nullptr) || (cu->editfont != nullptr);
cache->is_dirty = false;
}
void DRW_curve_batch_cache_validate(Curve *cu)
{
if (!curve_batch_cache_valid(cu)) {
curve_batch_cache_clear(cu);
curve_batch_cache_init(cu);
}
}
static CurveBatchCache *curve_batch_cache_get(Curve *cu)
{
return cu->batch_cache;
}
void DRW_curve_batch_cache_dirty_tag(Curve *cu, int mode)
{
CurveBatchCache *cache = cu->batch_cache;
if (cache == nullptr) {
return;
}
switch (mode) {
case BKE_CURVE_BATCH_DIRTY_ALL:
cache->is_dirty = true;
break;
case BKE_CURVE_BATCH_DIRTY_SELECT:
GPU_VERTBUF_DISCARD_SAFE(cache->edit.data);
GPU_BATCH_DISCARD_SAFE(cache->batch.edit_edges);
GPU_BATCH_DISCARD_SAFE(cache->batch.edit_verts);
break;
default:
BLI_assert(0);
}
}
static void curve_batch_cache_clear(Curve *cu)
{
CurveBatchCache *cache = cu->batch_cache;
if (!cache) {
return;
}
for (int i = 0; i < sizeof(cache->ordered) / sizeof(void *); i++) {
gpu::VertBuf **vbo = reinterpret_cast<gpu::VertBuf **>(&cache->ordered);
GPU_VERTBUF_DISCARD_SAFE(vbo[i]);
}
for (int i = 0; i < sizeof(cache->edit) / sizeof(void *); i++) {
gpu::VertBuf **vbo = reinterpret_cast<gpu::VertBuf **>(&cache->edit);
GPU_VERTBUF_DISCARD_SAFE(vbo[i]);
}
for (int i = 0; i < sizeof(cache->ibo) / sizeof(void *); i++) {
gpu::IndexBuf **ibo = reinterpret_cast<gpu::IndexBuf **>(&cache->ibo);
GPU_INDEXBUF_DISCARD_SAFE(ibo[i]);
}
for (int i = 0; i < sizeof(cache->batch) / sizeof(void *); i++) {
gpu::Batch **batch = reinterpret_cast<gpu::Batch **>(&cache->batch);
GPU_BATCH_DISCARD_SAFE(batch[i]);
}
}
void DRW_curve_batch_cache_free(Curve *cu)
{
curve_batch_cache_clear(cu);
MEM_SAFE_DELETE(cu->batch_cache);
}
/* -------------------------------------------------------------------- */
/** \name Private Curve Cache API
* \{ */
/* gpu::Batch cache usage. */
static void curve_create_curves_pos(CurveRenderData *rdata, gpu::VertBuf *vbo_curves_pos)
{
if (rdata->curve_eval == nullptr) {
return;
}
static struct {
uint pos;
} attr_id;
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
return format;
}();
const int vert_len = curve_render_data_wire_verts_len_get(rdata);
GPU_vertbuf_init_with_format(*vbo_curves_pos, format);
GPU_vertbuf_data_alloc(*vbo_curves_pos, vert_len);
const bke::CurvesGeometry &curves = rdata->curve_eval->geometry.wrap();
const Span<float3> positions = curves.evaluated_positions();
GPU_vertbuf_attr_fill(vbo_curves_pos, attr_id.pos, positions.data());
}
static void curve_create_attribute(CurveRenderData *rdata, gpu::VertBuf &vbo_attr)
{
if (rdata->curve_eval == nullptr) {
return;
}
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "attribute_value", gpu::VertAttrType::SFLOAT_32_32_32_32);
return format;
}();
const int vert_len = curve_render_data_wire_verts_len_get(rdata);
GPU_vertbuf_init_with_format(vbo_attr, format);
GPU_vertbuf_data_alloc(vbo_attr, vert_len);
const bke::CurvesGeometry &curves = rdata->curve_eval->geometry.wrap();
curves.ensure_can_interpolate_to_evaluated();
const VArraySpan colors = *curves.attributes().lookup<ColorGeometry4f>(".viewer",
bke::AttrDomain::Point);
curves.interpolate_to_evaluated(colors, vbo_attr.data<ColorGeometry4f>());
}
static void curve_create_curves_lines(CurveRenderData *rdata, gpu::IndexBuf *ibo_curve_lines)
{
if (rdata->curve_eval == nullptr) {
return;
}
const int vert_len = curve_render_data_wire_verts_len_get(rdata);
const int edge_len = curve_render_data_wire_edges_len_get(rdata);
const int curve_len = curve_render_data_wire_curve_len_get(rdata);
/* Count the last vertex or each strip and the primitive restart. */
const int index_len = edge_len + curve_len * 2;
GPUIndexBufBuilder elb;
GPU_indexbuf_init_ex(&elb, GPU_PRIM_LINE_STRIP, index_len, vert_len);
const bke::CurvesGeometry &curves = rdata->curve_eval->geometry.wrap();
const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
for (const int i : curves.curves_range()) {
const IndexRange points = points_by_curve[i];
if (cyclic[i] && points.size() > 1) {
GPU_indexbuf_add_generic_vert(&elb, points.last());
}
for (const int i_point : points) {
GPU_indexbuf_add_generic_vert(&elb, i_point);
}
GPU_indexbuf_add_primitive_restart(&elb);
}
GPU_indexbuf_build_in_place(&elb, ibo_curve_lines);
}
static void curve_create_edit_curves_nor(CurveRenderData *rdata,
gpu::VertBuf &vbo_curves_nor,
const Scene *scene)
{
const bool do_hq_normals = (scene->r.perf_flag & SCE_PERF_HQ_NORMALS) != 0 ||
GPU_use_hq_normals_workaround();
static struct {
uint pos, nor, tan, rad;
uint pos_hq, nor_hq, tan_hq, rad_hq;
} attr_id;
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
attr_id.rad = GPU_vertformat_attr_add(&format, "rad", gpu::VertAttrType::SFLOAT_32);
attr_id.nor = GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SNORM_10_10_10_2);
attr_id.tan = GPU_vertformat_attr_add(&format, "tangent", gpu::VertAttrType::SNORM_10_10_10_2);
return format;
}();
static const GPUVertFormat format_hq = [&]() {
GPUVertFormat format{};
attr_id.pos_hq = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
attr_id.rad_hq = GPU_vertformat_attr_add(&format, "rad", gpu::VertAttrType::SFLOAT_32);
attr_id.nor_hq = GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SNORM_16_16_16_16);
attr_id.tan_hq = GPU_vertformat_attr_add(
&format, "tangent", gpu::VertAttrType::SNORM_16_16_16_16);
return format;
}();
const GPUVertFormat &format_ptr = do_hq_normals ? format_hq : format;
int verts_len_capacity = curve_render_data_normal_len_get(rdata) * 2;
int vbo_len_used = 0;
GPU_vertbuf_init_with_format(vbo_curves_nor, format_ptr);
GPU_vertbuf_data_alloc(vbo_curves_nor, verts_len_capacity);
const BevList *bl;
const Nurb *nu;
const uint pos_id = do_hq_normals ? attr_id.pos_hq : attr_id.pos;
const uint nor_id = do_hq_normals ? attr_id.nor_hq : attr_id.nor;
const uint tan_id = do_hq_normals ? attr_id.tan_hq : attr_id.tan;
const uint rad_id = do_hq_normals ? attr_id.rad_hq : attr_id.rad;
for (bl = static_cast<const BevList *>(rdata->ob_curve_cache->bev.first),
nu = static_cast<const Nurb *>(rdata->nurbs->first);
nu && bl;
bl = bl->next, nu = nu->next)
{
const BevPoint *bevp = bl->bevpoints;
int nr = bl->nr;
int skip = nu->resolu / 16;
while (nr-- > 0) { /* accounts for empty bevel lists */
float nor[3] = {1.0f, 0.0f, 0.0f};
mul_qt_v3(bevp->quat, nor);
/* Only set attributes for one vertex. */
GPU_vertbuf_attr_set(&vbo_curves_nor, pos_id, vbo_len_used, bevp->vec);
GPU_vertbuf_attr_set(&vbo_curves_nor, rad_id, vbo_len_used, &bevp->radius);
if (do_hq_normals) {
const short4 pnor = gpu::convert_normal<short4>(nor);
const short4 ptan = gpu::convert_normal<short4>(bevp->dir);
GPU_vertbuf_attr_set(&vbo_curves_nor, nor_id, vbo_len_used, &pnor);
GPU_vertbuf_attr_set(&vbo_curves_nor, tan_id, vbo_len_used, &ptan);
}
else {
const gpu::PackedNormal pnor = gpu::convert_normal<gpu::PackedNormal>(nor);
const gpu::PackedNormal ptan = gpu::convert_normal<gpu::PackedNormal>(bevp->dir);
GPU_vertbuf_attr_set(&vbo_curves_nor, nor_id, vbo_len_used, &pnor);
GPU_vertbuf_attr_set(&vbo_curves_nor, tan_id, vbo_len_used, &ptan);
}
vbo_len_used++;
/* Skip the other vertex (it does not need to be offset). */
GPU_vertbuf_attr_set(&vbo_curves_nor, attr_id.pos, vbo_len_used, bevp->vec);
vbo_len_used++;
bevp += skip + 1;
nr -= skip;
}
}
BLI_assert(vbo_len_used == verts_len_capacity);
}
static uint8_t beztriple_vflag_get(CurveRenderData *rdata,
uint8_t flag,
uint8_t col_id,
int v_idx,
int nu_id,
bool handle_point,
const bool handle_selected)
{
uint8_t vflag = 0;
SET_FLAG_FROM_TEST(vflag, (flag & SELECT), VFLAG_VERT_SELECTED);
SET_FLAG_FROM_TEST(vflag, (v_idx == rdata->actvert && nu_id == rdata->actnu), VFLAG_VERT_ACTIVE);
SET_FLAG_FROM_TEST(vflag, (nu_id == rdata->actnu), ACTIVE_NURB);
SET_FLAG_FROM_TEST(vflag, handle_point, BEZIER_HANDLE);
SET_FLAG_FROM_TEST(vflag, handle_selected, VFLAG_VERT_SELECTED_BEZT_HANDLE);
/* Setting flags that overlap with will cause the color id not to work properly. */
BLI_assert((vflag >> COLOR_SHIFT) == 0);
/* handle color id */
vflag |= col_id << COLOR_SHIFT;
return vflag;
}
static uint8_t bpoint_vflag_get(CurveRenderData *rdata, uint8_t flag, int v_idx, int nu_id, int u)
{
uint8_t vflag = 0;
SET_FLAG_FROM_TEST(vflag, (flag & SELECT), VFLAG_VERT_SELECTED);
SET_FLAG_FROM_TEST(vflag, (v_idx == rdata->actvert && nu_id == rdata->actnu), VFLAG_VERT_ACTIVE);
SET_FLAG_FROM_TEST(vflag, (nu_id == rdata->actnu), ACTIVE_NURB);
SET_FLAG_FROM_TEST(vflag, ((u % 2) == 0), EVEN_U_BIT);
/* Setting flags that overlap with will cause the color id not to work properly. */
BLI_assert((vflag >> COLOR_SHIFT) == 0);
vflag |= COLOR_NURB_ULINE_ID << COLOR_SHIFT;
return vflag;
}
static void curve_create_edit_data_and_handles(CurveRenderData *rdata,
gpu::VertBuf *vbo_pos,
gpu::VertBuf *vbo_data,
gpu::IndexBuf *ibo_edit_verts_points,
gpu::IndexBuf *ibo_edit_lines)
{
static struct {
uint pos, data;
} attr_id;
static const GPUVertFormat format_pos = [&]() {
GPUVertFormat format{};
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
return format;
}();
static const GPUVertFormat format_data = [&]() {
GPUVertFormat format{};
attr_id.data = GPU_vertformat_attr_add(&format, "data", gpu::VertAttrType::UINT_32);
return format;
}();
int verts_len_capacity = curve_render_data_overlay_verts_len_get(rdata);
int edges_len_capacity = curve_render_data_overlay_edges_len_get(rdata) * 2;
int vbo_len_used = 0;
#define DRW_TEST_ASSIGN_VBO(v) (v = (DRW_vbo_requested(v) ? (v) : nullptr))
#define DRW_TEST_ASSIGN_IBO(v) (v = (DRW_ibo_requested(v) ? (v) : nullptr))
if (DRW_TEST_ASSIGN_VBO(vbo_pos)) {
GPU_vertbuf_init_with_format(*vbo_pos, format_pos);
GPU_vertbuf_data_alloc(*vbo_pos, verts_len_capacity);
}
if (DRW_TEST_ASSIGN_VBO(vbo_data)) {
GPU_vertbuf_init_with_format(*vbo_data, format_data);
GPU_vertbuf_data_alloc(*vbo_data, verts_len_capacity);
}
GPUIndexBufBuilder elb_verts, *elbp_verts = nullptr;
GPUIndexBufBuilder elb_lines, *elbp_lines = nullptr;
if (DRW_TEST_ASSIGN_IBO(ibo_edit_verts_points)) {
elbp_verts = &elb_verts;
GPU_indexbuf_init(elbp_verts, GPU_PRIM_POINTS, verts_len_capacity, verts_len_capacity);
}
if (DRW_TEST_ASSIGN_IBO(ibo_edit_lines)) {
elbp_lines = &elb_lines;
GPU_indexbuf_init(elbp_lines, GPU_PRIM_LINES, edges_len_capacity, verts_len_capacity);
}
#undef DRW_TEST_ASSIGN_VBO
#undef DRW_TEST_ASSIGN_IBO
int nu_id = 0;
for (Nurb *nu = static_cast<Nurb *>(rdata->nurbs->first); nu; nu = nu->next, nu_id++) {
const BezTriple *bezt = nu->bezt;
const BPoint *bp = nu->bp;
if (bezt) {
for (int a = 0; a < nu->pntsu; a++, bezt++) {
if (bezt->hide != 0) {
continue;
}
const bool handle_selected = BEZT_ISSEL_ANY(bezt);
if (elbp_verts) {
GPU_indexbuf_add_point_vert(elbp_verts, vbo_len_used + 0);
GPU_indexbuf_add_point_vert(elbp_verts, vbo_len_used + 1);
GPU_indexbuf_add_point_vert(elbp_verts, vbo_len_used + 2);
}
if (elbp_lines) {
GPU_indexbuf_add_line_verts(elbp_lines, vbo_len_used + 1, vbo_len_used + 0);
GPU_indexbuf_add_line_verts(elbp_lines, vbo_len_used + 1, vbo_len_used + 2);
}
if (vbo_data) {
const uint32_t vflag[3] = {
beztriple_vflag_get(rdata, bezt->f1, bezt->h1, a, nu_id, true, handle_selected),
beztriple_vflag_get(rdata, bezt->f2, bezt->h1, a, nu_id, false, handle_selected),
beztriple_vflag_get(rdata, bezt->f3, bezt->h2, a, nu_id, true, handle_selected),
};
for (int j = 0; j < 3; j++) {
GPU_vertbuf_attr_set(vbo_data, attr_id.data, vbo_len_used + j, &vflag[j]);
}
}
if (vbo_pos) {
for (int j = 0; j < 3; j++) {
GPU_vertbuf_attr_set(vbo_pos, attr_id.pos, vbo_len_used + j, bezt->vec[j]);
}
}
vbo_len_used += 3;
}
}
else if (bp) {
int pt_len = nu->pntsu * nu->pntsv;
for (int a = 0; a < pt_len; a++, bp++, vbo_len_used += 1) {
if (bp->hide != 0) {
continue;
}
int u = (a % nu->pntsu);
int v = (a / nu->pntsu);
/* Use indexed rendering for bezier.
* Specify all points and use indices to hide/show. */
if (elbp_verts) {
GPU_indexbuf_add_point_vert(elbp_verts, vbo_len_used);
}
if (elbp_lines) {
const BPoint *bp_next_u = (u < (nu->pntsu - 1)) ? &nu->bp[a + 1] : nullptr;
const BPoint *bp_next_v = (v < (nu->pntsv - 1)) ? &nu->bp[a + nu->pntsu] : nullptr;
if (bp_next_u && (bp_next_u->hide == false)) {
GPU_indexbuf_add_line_verts(elbp_lines, vbo_len_used, vbo_len_used + 1);
}
if (bp_next_v && (bp_next_v->hide == false)) {
GPU_indexbuf_add_line_verts(elbp_lines, vbo_len_used, vbo_len_used + nu->pntsu);
}
}
if (vbo_data) {
uint32_t vflag = bpoint_vflag_get(rdata, bp->f1, a, nu_id, u);
GPU_vertbuf_attr_set(vbo_data, attr_id.data, vbo_len_used, &vflag);
}
if (vbo_pos) {
GPU_vertbuf_attr_set(vbo_pos, attr_id.pos, vbo_len_used, bp->vec);
}
}
}
}
/* Resize & Finish */
if (elbp_verts != nullptr) {
GPU_indexbuf_build_in_place(elbp_verts, ibo_edit_verts_points);
}
if (elbp_lines != nullptr) {
GPU_indexbuf_build_in_place(elbp_lines, ibo_edit_lines);
}
if (vbo_len_used != verts_len_capacity) {
if (vbo_pos != nullptr) {
GPU_vertbuf_data_resize(*vbo_pos, vbo_len_used);
}
if (vbo_data != nullptr) {
GPU_vertbuf_data_resize(*vbo_data, vbo_len_used);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public Object/Curve API
* \{ */
gpu::Batch *DRW_curve_batch_cache_get_wire_edge(Curve *cu)
{
CurveBatchCache *cache = curve_batch_cache_get(cu);
return DRW_batch_request(&cache->batch.curves);
}
gpu::Batch *DRW_curve_batch_cache_get_wire_edge_viewer_attribute(Curve *cu)
{
CurveBatchCache *cache = curve_batch_cache_get(cu);
return DRW_batch_request(&cache->batch.curves_viewer_attribute);
}
gpu::Batch *DRW_curve_batch_cache_get_normal_edge(Curve *cu)
{
CurveBatchCache *cache = curve_batch_cache_get(cu);
return DRW_batch_request(&cache->batch.edit_normals);
}
gpu::Batch *DRW_curve_batch_cache_get_edit_edges(Curve *cu)
{
CurveBatchCache *cache = curve_batch_cache_get(cu);
return DRW_batch_request(&cache->batch.edit_edges);
}
gpu::Batch *DRW_curve_batch_cache_get_edit_verts(Curve *cu)
{
CurveBatchCache *cache = curve_batch_cache_get(cu);
return DRW_batch_request(&cache->batch.edit_verts);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Grouped batch generation
* \{ */
void DRW_curve_batch_cache_create_requested(Object *ob, const Scene *scene)
{
BLI_assert(ELEM(ob->type, OB_CURVES_LEGACY, OB_SURF, OB_FONT));
Curve *cu = id_cast<Curve *>(ob->data);
CurveBatchCache *cache = curve_batch_cache_get(cu);
/* Init batches and request VBOs & IBOs */
if (DRW_batch_requested(cache->batch.curves, GPU_PRIM_LINE_STRIP)) {
DRW_ibo_request(cache->batch.curves, &cache->ibo.curves_lines);
DRW_vbo_request(cache->batch.curves, &cache->ordered.curves_pos);
}
if (DRW_batch_requested(cache->batch.curves_viewer_attribute, GPU_PRIM_LINE_STRIP)) {
DRW_ibo_request(cache->batch.curves_viewer_attribute, &cache->ibo.curves_lines);
DRW_vbo_request(cache->batch.curves_viewer_attribute, &cache->ordered.curves_pos);
DRW_vbo_request(cache->batch.curves_viewer_attribute, &cache->ordered.attr_viewer);
}
/* Edit mode */
if (DRW_batch_requested(cache->batch.edit_edges, GPU_PRIM_LINES)) {
DRW_ibo_request(cache->batch.edit_edges, &cache->ibo.edit_lines);
DRW_vbo_request(cache->batch.edit_edges, &cache->edit.pos);
DRW_vbo_request(cache->batch.edit_edges, &cache->edit.data);
}
if (DRW_batch_requested(cache->batch.edit_verts, GPU_PRIM_POINTS)) {
DRW_ibo_request(cache->batch.edit_verts, &cache->ibo.edit_verts);
DRW_vbo_request(cache->batch.edit_verts, &cache->edit.pos);
DRW_vbo_request(cache->batch.edit_verts, &cache->edit.data);
}
if (DRW_batch_requested(cache->batch.edit_normals, GPU_PRIM_LINES)) {
DRW_vbo_request(cache->batch.edit_normals, &cache->edit.curves_nor);
}
#ifdef DRW_DEBUG_MESH_CACHE_REQUEST
printf("-- %s %s --\n", __func__, ob->id.name + 2);
#endif
/* Generate MeshRenderData flags */
int mr_flag = 0;
DRW_ADD_FLAG_FROM_VBO_REQUEST(mr_flag, cache->ordered.curves_pos, CU_DATATYPE_WIRE);
DRW_ADD_FLAG_FROM_VBO_REQUEST(
mr_flag, cache->ordered.attr_viewer, CU_DATATYPE_WIRE | CU_DATATYPE_OVERLAY);
DRW_ADD_FLAG_FROM_IBO_REQUEST(mr_flag, cache->ibo.curves_lines, CU_DATATYPE_WIRE);
DRW_ADD_FLAG_FROM_VBO_REQUEST(mr_flag, cache->edit.pos, CU_DATATYPE_OVERLAY);
DRW_ADD_FLAG_FROM_VBO_REQUEST(mr_flag, cache->edit.data, CU_DATATYPE_OVERLAY);
DRW_ADD_FLAG_FROM_VBO_REQUEST(mr_flag, cache->edit.curves_nor, CU_DATATYPE_NORMAL);
DRW_ADD_FLAG_FROM_IBO_REQUEST(mr_flag, cache->ibo.edit_verts, CU_DATATYPE_OVERLAY);
DRW_ADD_FLAG_FROM_IBO_REQUEST(mr_flag, cache->ibo.edit_lines, CU_DATATYPE_OVERLAY);
#ifdef DRW_DEBUG_MESH_CACHE_REQUEST
printf(" mr_flag %d\n\n", mr_flag);
#endif
CurveRenderData *rdata = curve_render_data_create(cu, ob->runtime->curve_cache, mr_flag);
/* Generate VBOs */
if (DRW_vbo_requested(cache->ordered.curves_pos)) {
curve_create_curves_pos(rdata, cache->ordered.curves_pos);
}
if (DRW_vbo_requested(cache->ordered.attr_viewer)) {
curve_create_attribute(rdata, *cache->ordered.attr_viewer);
}
if (DRW_ibo_requested(cache->ibo.curves_lines)) {
curve_create_curves_lines(rdata, cache->ibo.curves_lines);
}
if (DRW_vbo_requested(cache->edit.pos) || DRW_vbo_requested(cache->edit.data) ||
DRW_ibo_requested(cache->ibo.edit_verts) || DRW_ibo_requested(cache->ibo.edit_lines))
{
curve_create_edit_data_and_handles(
rdata, cache->edit.pos, cache->edit.data, cache->ibo.edit_verts, cache->ibo.edit_lines);
}
if (DRW_vbo_requested(cache->edit.curves_nor)) {
curve_create_edit_curves_nor(rdata, *cache->edit.curves_nor, scene);
}
curve_render_data_free(rdata);
#ifndef NDEBUG
/* Make sure all requested batches have been setup. */
for (int i = 0; i < sizeof(cache->batch) / sizeof(void *); i++) {
BLI_assert(!DRW_batch_requested(((gpu::Batch **)&cache->batch)[i], (GPUPrimType)0));
}
#endif
}
/** \} */
} // namespace blender::draw

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,502 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Lattice API for render engines
*/
#include "MEM_guardedalloc.h"
#include "BLI_utildefines.h"
#include "DNA_curve_types.h"
#include "DNA_lattice_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_deform.hh"
#include "BKE_lattice.hh"
#include "GPU_batch.hh"
#include "draw_cache_impl.hh" /* own include */
#define SELECT 1
namespace blender::draw {
static void lattice_batch_cache_clear(Lattice *lt);
/* ---------------------------------------------------------------------- */
/* Lattice Interface, direct access to basic data. */
static int vert_len_calc(int u, int v, int w)
{
if (u <= 0 || v <= 0 || w <= 0) {
return 0;
}
return u * v * w;
}
static int edge_len_calc(int u, int v, int w)
{
if (u <= 0 || v <= 0 || w <= 0) {
return 0;
}
return (((((u - 1) * v) + ((v - 1) * u)) * w) + ((w - 1) * (u * v)));
}
static int lattice_render_verts_len_get(Lattice *lt)
{
if (lt->editlatt) {
lt = lt->editlatt->latt;
}
const int u = lt->pntsu;
const int v = lt->pntsv;
const int w = lt->pntsw;
if ((lt->flag & LT_OUTSIDE) == 0) {
return vert_len_calc(u, v, w);
}
/* TODO: remove internal coords. */
return vert_len_calc(u, v, w);
}
static int lattice_render_edges_len_get(Lattice *lt)
{
if (lt->editlatt) {
lt = lt->editlatt->latt;
}
const int u = lt->pntsu;
const int v = lt->pntsv;
const int w = lt->pntsw;
if ((lt->flag & LT_OUTSIDE) == 0) {
return edge_len_calc(u, v, w);
}
/* TODO: remove internal coords. */
return edge_len_calc(u, v, w);
}
/* ---------------------------------------------------------------------- */
/* Lattice Interface, indirect, partially cached access to complex data. */
struct LatticeRenderData {
int types;
int vert_len;
int edge_len;
struct {
int u_len, v_len, w_len;
} dims;
bool show_only_outside;
EditLatt *edit_latt;
BPoint *bp;
int actbp;
const MDeformVert *dvert;
};
enum {
LR_DATATYPE_VERT = 1 << 0,
LR_DATATYPE_EDGE = 1 << 1,
LR_DATATYPE_OVERLAY = 1 << 2,
};
static LatticeRenderData *lattice_render_data_create(Lattice *lt, const int types)
{
LatticeRenderData *rdata = MEM_new_zeroed<LatticeRenderData>(__func__);
rdata->types = types;
if (lt->editlatt) {
EditLatt *editlatt = lt->editlatt;
lt = editlatt->latt;
rdata->edit_latt = editlatt;
rdata->dvert = lt->dvert;
if (types & (LR_DATATYPE_VERT)) {
rdata->vert_len = lattice_render_verts_len_get(lt);
}
if (types & (LR_DATATYPE_EDGE)) {
rdata->edge_len = lattice_render_edges_len_get(lt);
}
if (types & LR_DATATYPE_OVERLAY) {
rdata->actbp = lt->actbp;
}
}
else {
rdata->dvert = nullptr;
if (types & (LR_DATATYPE_VERT)) {
rdata->vert_len = lattice_render_verts_len_get(lt);
}
if (types & (LR_DATATYPE_EDGE)) {
rdata->edge_len = lattice_render_edges_len_get(lt);
/* No edge data. */
}
}
rdata->bp = lt->def;
rdata->dims.u_len = lt->pntsu;
rdata->dims.v_len = lt->pntsv;
rdata->dims.w_len = lt->pntsw;
rdata->show_only_outside = (lt->flag & LT_OUTSIDE) != 0;
rdata->actbp = lt->actbp;
return rdata;
}
static void lattice_render_data_free(LatticeRenderData *rdata)
{
#if 0
if (rdata->loose_verts) {
MEM_delete(rdata->loose_verts);
}
#endif
MEM_delete(rdata);
}
static int lattice_render_data_verts_len_get(const LatticeRenderData *rdata)
{
BLI_assert(rdata->types & LR_DATATYPE_VERT);
return rdata->vert_len;
}
static int lattice_render_data_edges_len_get(const LatticeRenderData *rdata)
{
BLI_assert(rdata->types & LR_DATATYPE_EDGE);
return rdata->edge_len;
}
static const BPoint *lattice_render_data_vert_bpoint(const LatticeRenderData *rdata,
const int vert_idx)
{
BLI_assert(rdata->types & LR_DATATYPE_VERT);
return &rdata->bp[vert_idx];
}
/* ---------------------------------------------------------------------- */
/* Lattice gpu::Batch Cache */
struct LatticeBatchCache {
gpu::VertBuf *pos;
gpu::IndexBuf *edges;
gpu::Batch *all_verts;
gpu::Batch *all_edges;
gpu::Batch *overlay_verts;
/* settings to determine if cache is invalid */
bool is_dirty;
struct {
int u_len, v_len, w_len;
} dims;
bool show_only_outside;
bool is_editmode;
};
/* gpu::Batch cache management. */
static bool lattice_batch_cache_valid(Lattice *lt)
{
LatticeBatchCache *cache = lt->batch_cache;
if (cache == nullptr) {
return false;
}
if (cache->is_editmode != (lt->editlatt != nullptr)) {
return false;
}
if (cache->is_dirty) {
return false;
}
if ((cache->dims.u_len != lt->pntsu) || (cache->dims.v_len != lt->pntsv) ||
(cache->dims.w_len != lt->pntsw) ||
(cache->show_only_outside != ((lt->flag & LT_OUTSIDE) != 0)))
{
return false;
}
return true;
}
static void lattice_batch_cache_init(Lattice *lt)
{
LatticeBatchCache *cache = lt->batch_cache;
if (!cache) {
lt->batch_cache = cache = MEM_new_zeroed<LatticeBatchCache>(__func__);
}
else {
memset(cache, 0, sizeof(*cache));
}
cache->dims.u_len = lt->pntsu;
cache->dims.v_len = lt->pntsv;
cache->dims.w_len = lt->pntsw;
cache->show_only_outside = (lt->flag & LT_OUTSIDE) != 0;
cache->is_editmode = lt->editlatt != nullptr;
cache->is_dirty = false;
}
void DRW_lattice_batch_cache_validate(Lattice *lt)
{
if (!lattice_batch_cache_valid(lt)) {
lattice_batch_cache_clear(lt);
lattice_batch_cache_init(lt);
}
}
static LatticeBatchCache *lattice_batch_cache_get(Lattice *lt)
{
return lt->batch_cache;
}
void DRW_lattice_batch_cache_dirty_tag(Lattice *lt, int mode)
{
LatticeBatchCache *cache = lt->batch_cache;
if (cache == nullptr) {
return;
}
switch (mode) {
case BKE_LATTICE_BATCH_DIRTY_ALL:
cache->is_dirty = true;
break;
case BKE_LATTICE_BATCH_DIRTY_SELECT:
/* TODO: Separate Flag VBO. */
GPU_BATCH_DISCARD_SAFE(cache->overlay_verts);
break;
default:
BLI_assert(0);
}
}
static void lattice_batch_cache_clear(Lattice *lt)
{
LatticeBatchCache *cache = lt->batch_cache;
if (!cache) {
return;
}
GPU_BATCH_DISCARD_SAFE(cache->all_verts);
GPU_BATCH_DISCARD_SAFE(cache->all_edges);
GPU_BATCH_DISCARD_SAFE(cache->overlay_verts);
GPU_VERTBUF_DISCARD_SAFE(cache->pos);
GPU_INDEXBUF_DISCARD_SAFE(cache->edges);
}
void DRW_lattice_batch_cache_free(Lattice *lt)
{
lattice_batch_cache_clear(lt);
MEM_SAFE_DELETE(lt->batch_cache);
}
/* gpu::Batch cache usage. */
static gpu::VertBuf *lattice_batch_cache_get_pos(LatticeRenderData *rdata,
LatticeBatchCache *cache,
bool use_weight,
const int actdef)
{
BLI_assert(rdata->types & LR_DATATYPE_VERT);
if (cache->pos == nullptr) {
GPUVertFormat format = {0};
struct {
uint pos, col;
} attr_id;
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
if (use_weight) {
attr_id.col = GPU_vertformat_attr_add(&format, "weight", gpu::VertAttrType::SFLOAT_32);
}
const int vert_len = lattice_render_data_verts_len_get(rdata);
cache->pos = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*cache->pos, vert_len);
for (int i = 0; i < vert_len; i++) {
const BPoint *bp = lattice_render_data_vert_bpoint(rdata, i);
GPU_vertbuf_attr_set(cache->pos, attr_id.pos, i, bp->vec);
if (use_weight) {
const float no_active_weight = 666.0f;
float weight = (actdef > -1) ? BKE_defvert_find_weight(rdata->dvert + i, actdef) :
no_active_weight;
GPU_vertbuf_attr_set(cache->pos, attr_id.col, i, &weight);
}
}
}
return cache->pos;
}
static gpu::IndexBuf *lattice_batch_cache_get_edges(LatticeRenderData *rdata,
LatticeBatchCache *cache)
{
BLI_assert(rdata->types & (LR_DATATYPE_VERT | LR_DATATYPE_EDGE));
if (cache->edges == nullptr) {
const int vert_len = lattice_render_data_verts_len_get(rdata);
const int edge_len = lattice_render_data_edges_len_get(rdata);
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, edge_len, vert_len);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
/* The buffer is allocated with **all** edges (see #lattice_render_edges_len_get()), but with
* the LT_OUTSIDE flag not all are drawn. So fill those gaps with zeros to hide redundant
* edges. */
data.fill(uint2(0));
int line_index = 0;
#define LATT_INDEX(u, v, w) ((((w) * rdata->dims.v_len + (v)) * rdata->dims.u_len) + (u))
for (int w = 0; w < rdata->dims.w_len; w++) {
int wxt = ELEM(w, 0, rdata->dims.w_len - 1);
for (int v = 0; v < rdata->dims.v_len; v++) {
int vxt = ELEM(v, 0, rdata->dims.v_len - 1);
for (int u = 0; u < rdata->dims.u_len; u++) {
int uxt = ELEM(u, 0, rdata->dims.u_len - 1);
if (w && ((uxt || vxt) || !rdata->show_only_outside)) {
data[line_index++] = uint2(LATT_INDEX(u, v, w - 1), LATT_INDEX(u, v, w));
}
if (v && ((uxt || wxt) || !rdata->show_only_outside)) {
data[line_index++] = uint2(LATT_INDEX(u, v - 1, w), LATT_INDEX(u, v, w));
}
if (u && ((vxt || wxt) || !rdata->show_only_outside)) {
data[line_index++] = uint2(LATT_INDEX(u - 1, v, w), LATT_INDEX(u, v, w));
}
}
}
}
#undef LATT_INDEX
if (rdata->show_only_outside) {
BLI_assert(line_index <= edge_len);
}
else {
BLI_assert(line_index == edge_len);
}
cache->edges = GPU_indexbuf_build_ex(&builder, 0, vert_len, false);
}
return cache->edges;
}
static void lattice_batch_cache_create_overlay_batches(Lattice *lt)
{
/* Since LR_DATATYPE_OVERLAY is slow to generate, generate them all at once */
int options = LR_DATATYPE_VERT | LR_DATATYPE_OVERLAY;
LatticeBatchCache *cache = lattice_batch_cache_get(lt);
LatticeRenderData *rdata = lattice_render_data_create(lt, options);
if (cache->overlay_verts == nullptr) {
static struct {
uint pos, data;
} attr_id;
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
attr_id.pos = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
attr_id.data = GPU_vertformat_attr_add(&format, "data", gpu::VertAttrType::UINT_32);
return format;
}();
const int vert_len = lattice_render_data_verts_len_get(rdata);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo, vert_len);
for (int i = 0; i < vert_len; i++) {
const BPoint *bp = lattice_render_data_vert_bpoint(rdata, i);
uint32_t vflag = 0;
if (bp->f1 & SELECT) {
if (i == rdata->actbp) {
vflag |= VFLAG_VERT_ACTIVE;
}
else {
vflag |= VFLAG_VERT_SELECTED;
}
}
GPU_vertbuf_attr_set(vbo, attr_id.pos, i, bp->vec);
GPU_vertbuf_attr_set(vbo, attr_id.data, i, &vflag);
}
cache->overlay_verts = GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, nullptr, GPU_BATCH_OWNS_VBO);
}
lattice_render_data_free(rdata);
}
gpu::Batch *DRW_lattice_batch_cache_get_all_edges(Lattice *lt, bool use_weight, const int actdef)
{
LatticeBatchCache *cache = lattice_batch_cache_get(lt);
if (cache->all_edges == nullptr) {
/* create batch from Lattice */
LatticeRenderData *rdata = lattice_render_data_create(lt, LR_DATATYPE_VERT | LR_DATATYPE_EDGE);
cache->all_edges = GPU_batch_create(
GPU_PRIM_LINES,
lattice_batch_cache_get_pos(rdata, cache, use_weight, actdef),
lattice_batch_cache_get_edges(rdata, cache));
lattice_render_data_free(rdata);
}
return cache->all_edges;
}
gpu::Batch *DRW_lattice_batch_cache_get_all_verts(Lattice *lt)
{
LatticeBatchCache *cache = lattice_batch_cache_get(lt);
if (cache->all_verts == nullptr) {
LatticeRenderData *rdata = lattice_render_data_create(lt, LR_DATATYPE_VERT);
cache->all_verts = GPU_batch_create(
GPU_PRIM_POINTS, lattice_batch_cache_get_pos(rdata, cache, false, -1), nullptr);
lattice_render_data_free(rdata);
}
return cache->all_verts;
}
gpu::Batch *DRW_lattice_batch_cache_get_edit_verts(Lattice *lt)
{
LatticeBatchCache *cache = lattice_batch_cache_get(lt);
if (cache->overlay_verts == nullptr) {
lattice_batch_cache_create_overlay_batches(lt);
}
return cache->overlay_verts;
}
} // namespace blender::draw

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,462 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief PointCloud API for render engines
*/
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_color_types.hh"
#include "BLI_listbase.h"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "DNA_object_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_userdef_types.h"
#include "BKE_attribute.hh"
#include "BKE_material.hh"
#include "BKE_pointcloud.hh"
#include "GPU_batch.hh"
#include "GPU_material.hh"
#include "DRW_render.hh"
#include "draw_attributes.hh"
#include "draw_cache_impl.hh"
#include "draw_cache_inline.hh"
#include "draw_pointcloud_private.hh" /* own include */
#include "draw_defines.hh"
namespace blender::draw {
/* -------------------------------------------------------------------- */
/** \name gpu::Batch cache management
* \{ */
struct PointCloudEvalCache {
/* Dot primitive types. */
gpu::Batch *dots;
/* Triangle primitive types. */
gpu::Batch *surface;
gpu::Batch **surface_per_mat;
/* Position and radius. */
gpu::VertBuf *pos_rad;
/* Active attribute in 3D view. */
gpu::VertBuf *attr_viewer;
/* Requested attributes */
gpu::VertBuf *attributes_buf[GPU_MAX_ATTR];
/** Attributes currently being drawn or about to be drawn. */
VectorSet<std::string> attr_used;
/**
* Attributes that were used at some point. This is used for garbage collection, to remove
* attributes that are not used in shaders anymore due to user edits.
*/
VectorSet<std::string> attr_used_over_time;
/**
* The last time in seconds that the `attr_used` and `attr_used_over_time` were exactly the same.
* If the delta between this time and the current scene time is greater than the timeout set in
* user preferences (`U.vbotimeout`) then garbage collection is performed.
*/
int last_attr_matching_time;
int mat_len;
};
struct PointCloudBatchCache {
PointCloudEvalCache eval_cache;
gpu::IndexBuf *edit_selection_indices = nullptr;
gpu::Batch *edit_selection = nullptr;
/* settings to determine if cache is invalid */
bool is_dirty;
};
static PointCloudBatchCache *pointcloud_batch_cache_get(PointCloud &pointcloud)
{
return pointcloud.batch_cache;
}
static bool pointcloud_batch_cache_valid(PointCloud &pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud);
if (cache == nullptr) {
return false;
}
if (cache->eval_cache.mat_len != BKE_id_material_used_with_fallback_eval(pointcloud.id)) {
return false;
}
return cache->is_dirty == false;
}
static void pointcloud_batch_cache_init(PointCloud &pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud);
if (!cache) {
cache = MEM_new<PointCloudBatchCache>(__func__);
pointcloud.batch_cache = cache;
}
else {
cache->eval_cache = {};
cache->edit_selection = nullptr;
cache->edit_selection_indices = nullptr;
}
cache->eval_cache.mat_len = BKE_id_material_used_with_fallback_eval(pointcloud.id);
cache->eval_cache.surface_per_mat = MEM_new_array_zeroed<gpu::Batch *>(cache->eval_cache.mat_len,
__func__);
cache->is_dirty = false;
}
void DRW_pointcloud_batch_cache_dirty_tag(PointCloud *pointcloud, int mode)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(*pointcloud);
if (cache == nullptr) {
return;
}
switch (mode) {
case BKE_POINTCLOUD_BATCH_DIRTY_ALL:
cache->is_dirty = true;
break;
default:
BLI_assert(0);
}
}
static void pointcloud_discard_attributes(PointCloudBatchCache &cache)
{
for (const int j : IndexRange(GPU_MAX_ATTR)) {
GPU_VERTBUF_DISCARD_SAFE(cache.eval_cache.attributes_buf[j]);
}
cache.eval_cache.attr_used.clear();
}
static void pointcloud_batch_cache_clear(PointCloud &pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud);
if (!cache) {
return;
}
GPU_BATCH_DISCARD_SAFE(cache->eval_cache.dots);
GPU_BATCH_DISCARD_SAFE(cache->eval_cache.surface);
GPU_VERTBUF_DISCARD_SAFE(cache->eval_cache.pos_rad);
GPU_VERTBUF_DISCARD_SAFE(cache->eval_cache.attr_viewer);
GPU_INDEXBUF_DISCARD_SAFE(cache->edit_selection_indices);
GPU_BATCH_DISCARD_SAFE(cache->edit_selection);
if (cache->eval_cache.surface_per_mat) {
for (int i = 0; i < cache->eval_cache.mat_len; i++) {
GPU_BATCH_DISCARD_SAFE(cache->eval_cache.surface_per_mat[i]);
}
}
MEM_SAFE_DELETE(cache->eval_cache.surface_per_mat);
pointcloud_discard_attributes(*cache);
}
void DRW_pointcloud_batch_cache_validate(PointCloud *pointcloud)
{
if (!pointcloud_batch_cache_valid(*pointcloud)) {
pointcloud_batch_cache_clear(*pointcloud);
pointcloud_batch_cache_init(*pointcloud);
}
}
void DRW_pointcloud_batch_cache_free(PointCloud *pointcloud)
{
pointcloud_batch_cache_clear(*pointcloud);
MEM_delete(pointcloud->batch_cache);
pointcloud->batch_cache = nullptr;
}
void DRW_pointcloud_batch_cache_free_old(PointCloud *pointcloud, int ctime)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(*pointcloud);
if (!cache) {
return;
}
bool do_discard = false;
if (drw_attributes_overlap(&cache->eval_cache.attr_used_over_time, &cache->eval_cache.attr_used))
{
cache->eval_cache.last_attr_matching_time = ctime;
}
if (ctime - cache->eval_cache.last_attr_matching_time > U.vbotimeout) {
do_discard = true;
}
cache->eval_cache.attr_used_over_time.clear();
if (do_discard) {
pointcloud_discard_attributes(*cache);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name PointCloud extraction
* \{ */
static void pointcloud_extract_position_and_radius(const PointCloud &pointcloud,
PointCloudBatchCache &cache)
{
const bke::AttributeAccessor attributes = pointcloud.attributes();
const Span<float3> positions = pointcloud.positions();
const VArray<float> radii = *attributes.lookup<float>("radius");
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32_32);
GPU_vertformat_alias_add(&format, "pos_rad");
return format;
}();
GPUUsageType usage_flag = GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY;
GPU_vertbuf_init_with_format_ex(*cache.eval_cache.pos_rad, format, usage_flag);
GPU_vertbuf_data_alloc(*cache.eval_cache.pos_rad, positions.size());
MutableSpan<float4> vbo_data = cache.eval_cache.pos_rad->data<float4>();
if (radii) {
const VArraySpan<float> radii_span(std::move(radii));
threading::parallel_for(vbo_data.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
vbo_data[i].x = positions[i].x;
vbo_data[i].y = positions[i].y;
vbo_data[i].z = positions[i].z;
vbo_data[i].w = radii_span[i];
}
});
}
else {
threading::parallel_for(vbo_data.index_range(), 4096, [&](IndexRange range) {
for (const int i : range) {
vbo_data[i].x = positions[i].x;
vbo_data[i].y = positions[i].y;
vbo_data[i].z = positions[i].z;
vbo_data[i].w = 0.01f;
}
});
}
}
static void pointcloud_extract_attribute(const PointCloud &pointcloud,
PointCloudBatchCache &cache,
const StringRef name,
int index)
{
gpu::VertBuf &attr_buf = *cache.eval_cache.attributes_buf[index];
const bke::AttributeAccessor attributes = pointcloud.attributes();
/* TODO(@kevindietrich): float4 is used for scalar attributes as the implicit conversion done
* by OpenGL to float4 for a scalar `s` will produce a `float4(s, 0, 0, 1)`. However, following
* the Blender convention, it should be `float4(s, s, s, 1)`. This could be resolved using a
* similar texture state swizzle to map the attribute correctly as for volume attributes, so we
* can control the conversion ourselves. */
bke::AttributeReader<ColorGeometry4f> attribute = attributes.lookup_or_default<ColorGeometry4f>(
name, bke::AttrDomain::Point, {0.0f, 0.0f, 0.0f, 1.0f});
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "attr", gpu::VertAttrType::SFLOAT_32_32_32_32);
return format;
}();
GPUUsageType usage_flag = GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY;
GPU_vertbuf_init_with_format_ex(attr_buf, format, usage_flag);
GPU_vertbuf_data_alloc(attr_buf, pointcloud.totpoint);
attribute.varray.materialize(attr_buf.data<ColorGeometry4f>());
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Private API
* \{ */
gpu::VertBuf *pointcloud_position_and_radius_get(PointCloud *pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(*pointcloud);
DRW_vbo_request(nullptr, &cache->eval_cache.pos_rad);
return cache->eval_cache.pos_rad;
}
gpu::Batch **pointcloud_surface_shaded_get(PointCloud *pointcloud,
GPUMaterial **gpu_materials,
int mat_len)
{
const bke::AttributeAccessor attributes = pointcloud->attributes();
PointCloudBatchCache *cache = pointcloud_batch_cache_get(*pointcloud);
VectorSet<std::string> attrs_needed;
for (GPUMaterial *gpu_material : Span<GPUMaterial *>(gpu_materials, mat_len)) {
ListBaseT<GPUMaterialAttribute> gpu_attrs = GPU_material_attributes(gpu_material);
for (GPUMaterialAttribute &gpu_attr : gpu_attrs) {
const StringRef name = gpu_attr.name;
if (!attributes.contains(name)) {
continue;
}
drw_attributes_add_request(&attrs_needed, name);
}
}
if (!drw_attributes_overlap(&cache->eval_cache.attr_used, &attrs_needed)) {
/* Some new attributes have been added, free all and start over. */
for (const int i : IndexRange(GPU_MAX_ATTR)) {
GPU_VERTBUF_DISCARD_SAFE(cache->eval_cache.attributes_buf[i]);
}
drw_attributes_merge(&cache->eval_cache.attr_used, &attrs_needed);
}
drw_attributes_merge(&cache->eval_cache.attr_used_over_time, &attrs_needed);
DRW_batch_request(&cache->eval_cache.surface_per_mat[0]);
return cache->eval_cache.surface_per_mat;
}
gpu::Batch *pointcloud_surface_get(PointCloud *pointcloud)
{
PointCloudBatchCache &cache = *pointcloud_batch_cache_get(*pointcloud);
if (cache.eval_cache.surface != nullptr) {
return cache.eval_cache.surface;
}
cache.eval_cache.surface = GPU_batch_create_procedural(
GPU_PRIM_TRI_STRIP, DRW_POINTCLOUD_STRIP_TILE_SIZE * pointcloud->totpoint);
DRW_vbo_request(nullptr, &cache.eval_cache.pos_rad);
return cache.eval_cache.surface;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name API
* \{ */
gpu::Batch *DRW_pointcloud_batch_cache_get_dots(Object *ob)
{
PointCloud &pointcloud = DRW_object_get_data_for_drawing<PointCloud>(*ob);
PointCloudBatchCache *cache = pointcloud_batch_cache_get(pointcloud);
return DRW_batch_request(&cache->eval_cache.dots);
}
gpu::VertBuf *DRW_pointcloud_position_and_radius_buffer_get(Object *ob)
{
PointCloud &pointcloud = DRW_object_get_data_for_drawing<PointCloud>(*ob);
return pointcloud_position_and_radius_get(&pointcloud);
}
gpu::VertBuf **DRW_pointcloud_evaluated_attribute(PointCloud *pointcloud, const StringRef name)
{
const bke::AttributeAccessor attributes = pointcloud->attributes();
PointCloudBatchCache &cache = *pointcloud_batch_cache_get(*pointcloud);
if (!attributes.contains(name)) {
return nullptr;
}
{
VectorSet<std::string> requests{};
drw_attributes_add_request(&requests, name);
drw_attributes_merge(&cache.eval_cache.attr_used, &requests);
}
int request_i = -1;
for (const int i : IndexRange(cache.eval_cache.attr_used.index_range())) {
if (cache.eval_cache.attr_used[i] == name) {
request_i = i;
break;
}
}
if (request_i == -1) {
return nullptr;
}
return &cache.eval_cache.attributes_buf[request_i];
}
static void index_mask_to_ibo(const IndexMask &mask, gpu::IndexBuf &ibo)
{
const int max_index = mask.min_array_size();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, mask.size(), max_index);
MutableSpan<uint> data = GPU_indexbuf_get_data(&builder);
mask.to_indices<int>(data.cast<int>());
GPU_indexbuf_build_in_place_ex(&builder, 0, max_index, false, &ibo);
}
static void build_edit_selection_indices(const PointCloud &pointcloud, gpu::IndexBuf &ibo)
{
const VArray selection = *pointcloud.attributes().lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_bools(selection, memory);
if (mask.is_empty()) {
return;
}
index_mask_to_ibo(mask, ibo);
}
void DRW_pointcloud_batch_cache_create_requested(Object *ob)
{
PointCloud &pointcloud = DRW_object_get_data_for_drawing<PointCloud>(*ob);
PointCloudBatchCache &cache = *pointcloud_batch_cache_get(pointcloud);
if (DRW_batch_requested(cache.eval_cache.dots, GPU_PRIM_POINTS)) {
DRW_vbo_request(cache.eval_cache.dots, &cache.eval_cache.pos_rad);
}
if (DRW_batch_requested(cache.edit_selection, GPU_PRIM_POINTS)) {
DRW_ibo_request(cache.edit_selection, &cache.edit_selection_indices);
DRW_vbo_request(cache.edit_selection, &cache.eval_cache.pos_rad);
}
for (int i = 0; i < cache.eval_cache.mat_len; i++) {
if (DRW_batch_requested(cache.eval_cache.surface_per_mat[i], GPU_PRIM_TRIS)) {
/* TODO(fclem): Per material ranges. */
}
}
for (const int j : cache.eval_cache.attr_used.index_range()) {
DRW_vbo_request(nullptr, &cache.eval_cache.attributes_buf[j]);
if (DRW_vbo_requested(cache.eval_cache.attributes_buf[j])) {
pointcloud_extract_attribute(pointcloud, cache, cache.eval_cache.attr_used[j], j);
}
}
if (DRW_ibo_requested(cache.edit_selection_indices)) {
build_edit_selection_indices(pointcloud, *cache.edit_selection_indices);
}
if (DRW_vbo_requested(cache.eval_cache.pos_rad)) {
pointcloud_extract_position_and_radius(pointcloud, cache);
}
}
gpu::Batch *DRW_pointcloud_batch_cache_get_edit_dots(PointCloud *pointcloud)
{
PointCloudBatchCache *cache = pointcloud_batch_cache_get(*pointcloud);
return DRW_batch_request(&cache->edit_selection);
}
/** \} */
} // namespace blender::draw

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,351 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Volume API for render engines
*/
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_matrix.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_scene_types.h"
#include "DNA_volume_types.h"
#include "BKE_global.hh"
#include "BKE_volume.hh"
#include "BKE_volume_grid_fwd.hh"
#include "BKE_volume_render.hh"
#include "GPU_attribute_convert.hh"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "GPU_texture.hh"
#include "DRW_render.hh"
#include "draw_cache.hh" /* own include */
#include "draw_cache_impl.hh" /* own include */
namespace blender::draw {
static void volume_batch_cache_clear(Volume *volume);
/* ---------------------------------------------------------------------- */
/* Volume gpu::Batch Cache */
struct VolumeBatchCache {
/* 3D textures */
ListBaseT<DRWVolumeGrid> grids;
/* Wireframe */
struct {
gpu::VertBuf *pos_nor_in_order;
gpu::Batch *batch;
} face_wire;
/* Surface for selection */
gpu::Batch *selection_surface;
/* settings to determine if cache is invalid */
bool is_dirty;
};
/* gpu::Batch cache management. */
static bool volume_batch_cache_valid(Volume *volume)
{
VolumeBatchCache *cache = volume->batch_cache;
return (cache && cache->is_dirty == false);
}
static void volume_batch_cache_init(Volume *volume)
{
VolumeBatchCache *cache = volume->batch_cache;
if (!cache) {
volume->batch_cache = cache = MEM_new_zeroed<VolumeBatchCache>(__func__);
}
else {
memset(cache, 0, sizeof(*cache));
}
cache->is_dirty = false;
}
void DRW_volume_batch_cache_validate(Volume *volume)
{
if (!volume_batch_cache_valid(volume)) {
volume_batch_cache_clear(volume);
volume_batch_cache_init(volume);
}
}
static VolumeBatchCache *volume_batch_cache_get(Volume *volume)
{
DRW_volume_batch_cache_validate(volume);
return volume->batch_cache;
}
void DRW_volume_batch_cache_dirty_tag(Volume *volume, int mode)
{
VolumeBatchCache *cache = volume->batch_cache;
if (cache == nullptr) {
return;
}
switch (mode) {
case BKE_VOLUME_BATCH_DIRTY_ALL:
cache->is_dirty = true;
break;
default:
BLI_assert(0);
}
}
static void volume_batch_cache_clear(Volume *volume)
{
VolumeBatchCache *cache = volume->batch_cache;
if (!cache) {
return;
}
for (DRWVolumeGrid &grid : cache->grids) {
MEM_SAFE_DELETE(grid.name);
GPU_TEXTURE_FREE_SAFE(grid.texture);
}
cache->grids.free_no_destruct();
GPU_VERTBUF_DISCARD_SAFE(cache->face_wire.pos_nor_in_order);
GPU_BATCH_DISCARD_SAFE(cache->face_wire.batch);
GPU_BATCH_DISCARD_SAFE(cache->selection_surface);
}
void DRW_volume_batch_cache_free(Volume *volume)
{
volume_batch_cache_clear(volume);
MEM_SAFE_DELETE(volume->batch_cache);
}
struct VolumeWireframeUserData {
Volume *volume;
Scene *scene;
};
static void drw_volume_wireframe_cb(
void *userdata, const float (*verts)[3], const int (*edges)[2], int totvert, int totedge)
{
VolumeWireframeUserData *data = static_cast<VolumeWireframeUserData *>(userdata);
Scene *scene = data->scene;
Volume *volume = data->volume;
VolumeBatchCache *cache = volume->batch_cache;
const bool do_hq_normals = (scene->r.perf_flag & SCE_PERF_HQ_NORMALS) != 0 ||
GPU_use_hq_normals_workaround();
/* Create vertex buffer. */
static struct {
uint pos_id, nor_id;
uint pos_hq_id, nor_hq_id;
} attr_id;
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
attr_id.pos_id = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
attr_id.nor_id = GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SNORM_10_10_10_2);
return format;
}();
static const GPUVertFormat format_hq = [&]() {
GPUVertFormat format{};
attr_id.pos_hq_id = GPU_vertformat_attr_add(
&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
attr_id.nor_hq_id = GPU_vertformat_attr_add(
&format, "nor", gpu::VertAttrType::SNORM_16_16_16_16);
return format;
}();
uint pos_id = do_hq_normals ? attr_id.pos_hq_id : attr_id.pos_id;
uint nor_id = do_hq_normals ? attr_id.nor_hq_id : attr_id.nor_id;
cache->face_wire.pos_nor_in_order = GPU_vertbuf_create_with_format(do_hq_normals ? format_hq :
format);
GPU_vertbuf_data_alloc(*cache->face_wire.pos_nor_in_order, totvert);
GPU_vertbuf_attr_fill(cache->face_wire.pos_nor_in_order, pos_id, verts);
const float3 normal(1.0f, 0.0f, 0.0f);
if (do_hq_normals) {
const gpu::PackedNormal packed_normal = gpu::convert_normal<gpu::PackedNormal>(normal);
GPU_vertbuf_attr_fill_stride(cache->face_wire.pos_nor_in_order, nor_id, 0, &packed_normal);
}
else {
const short4 packed_normal = gpu::convert_normal<short4>(normal);
GPU_vertbuf_attr_fill_stride(cache->face_wire.pos_nor_in_order, nor_id, 0, &packed_normal);
}
/* Create wiredata. */
gpu::VertBuf *vbo_wiredata = GPU_vertbuf_calloc();
DRW_vertbuf_create_wiredata(vbo_wiredata, totvert);
if (volume->display.wireframe_type == VOLUME_WIREFRAME_POINTS) {
/* Create batch. */
cache->face_wire.batch = GPU_batch_create(
GPU_PRIM_POINTS, cache->face_wire.pos_nor_in_order, nullptr);
}
else {
/* Create edge index buffer. */
GPUIndexBufBuilder elb;
GPU_indexbuf_init(&elb, GPU_PRIM_LINES, totedge, totvert);
for (int i = 0; i < totedge; i++) {
GPU_indexbuf_add_line_verts(&elb, edges[i][0], edges[i][1]);
}
gpu::IndexBuf *ibo = GPU_indexbuf_build(&elb);
/* Create batch. */
cache->face_wire.batch = GPU_batch_create_ex(
GPU_PRIM_LINES, cache->face_wire.pos_nor_in_order, ibo, GPU_BATCH_OWNS_INDEX);
}
GPU_batch_vertbuf_add(cache->face_wire.batch, vbo_wiredata, true);
}
gpu::Batch *DRW_volume_batch_cache_get_wireframes_face(Volume *volume)
{
if (volume->display.wireframe_type == VOLUME_WIREFRAME_NONE) {
return nullptr;
}
VolumeBatchCache *cache = volume_batch_cache_get(volume);
if (cache->face_wire.batch == nullptr) {
const bke::VolumeGridData *volume_grid = BKE_volume_grid_active_get_for_read(volume);
if (volume_grid == nullptr) {
return nullptr;
}
/* Create wireframe from OpenVDB tree. */
const DRWContext *draw_ctx = DRW_context_get();
VolumeWireframeUserData userdata;
userdata.volume = volume;
userdata.scene = draw_ctx->scene;
BKE_volume_grid_wireframe(volume, volume_grid, drw_volume_wireframe_cb, &userdata);
}
return cache->face_wire.batch;
}
static void drw_volume_selection_surface_cb(
void *userdata, float (*verts)[3], int (*tris)[3], int totvert, int tottris)
{
Volume *volume = static_cast<Volume *>(userdata);
VolumeBatchCache *cache = volume->batch_cache;
static uint pos_id;
static const GPUVertFormat format = [&]() {
GPUVertFormat format{};
pos_id = GPU_vertformat_attr_add(&format, "pos", gpu::VertAttrType::SFLOAT_32_32_32);
return format;
}();
/* Create vertex buffer. */
gpu::VertBuf *vbo_surface = GPU_vertbuf_create_with_format(format);
GPU_vertbuf_data_alloc(*vbo_surface, totvert);
GPU_vertbuf_attr_fill(vbo_surface, pos_id, verts);
/* Create index buffer. */
GPUIndexBufBuilder elb;
GPU_indexbuf_init(&elb, GPU_PRIM_TRIS, tottris, totvert);
for (int i = 0; i < tottris; i++) {
GPU_indexbuf_add_tri_verts(&elb, UNPACK3(tris[i]));
}
gpu::IndexBuf *ibo_surface = GPU_indexbuf_build(&elb);
cache->selection_surface = GPU_batch_create_ex(
GPU_PRIM_TRIS, vbo_surface, ibo_surface, GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
}
gpu::Batch *DRW_volume_batch_cache_get_selection_surface(Volume *volume)
{
VolumeBatchCache *cache = volume_batch_cache_get(volume);
if (cache->selection_surface == nullptr) {
const bke::VolumeGridData *volume_grid = BKE_volume_grid_active_get_for_read(volume);
if (volume_grid == nullptr) {
return nullptr;
}
BKE_volume_grid_selection_surface(
volume, volume_grid, drw_volume_selection_surface_cb, volume);
}
return cache->selection_surface;
}
static DRWVolumeGrid *volume_grid_cache_get(const Volume *volume,
const bke::VolumeGridData *grid,
VolumeBatchCache *cache)
{
const std::string name = bke::volume_grid::get_name(*grid);
/* Return cached grid. */
for (DRWVolumeGrid &cache_grid : cache->grids) {
if (cache_grid.name == name) {
return &cache_grid;
}
}
/* Allocate new grid. */
DRWVolumeGrid *cache_grid = MEM_new_zeroed<DRWVolumeGrid>(__func__);
cache_grid->name = BLI_strdup(name.c_str());
BLI_addtail(&cache->grids, cache_grid);
/* TODO: can we load this earlier, avoid accessing the global and take
* advantage of dependency graph multi-threading? */
BKE_volume_load(volume, G.main);
/* Test if we support textures with the number of channels. */
size_t channels = bke::volume_grid::get_channels_num(bke::volume_grid::get_type(*grid));
if (!ELEM(channels, 1, 3)) {
return cache_grid;
}
DenseFloatVolumeGrid dense_grid;
if (BKE_volume_grid_dense_floats(volume, grid, &dense_grid)) {
cache_grid->texture_to_object = float4x4(dense_grid.texture_to_object);
cache_grid->object_to_texture = math::invert(cache_grid->texture_to_object);
/* Create GPU texture. */
gpu::TextureFormat format = (channels == 3) ? gpu::TextureFormat::SFLOAT_16_16_16 :
gpu::TextureFormat::SFLOAT_16;
cache_grid->texture = GPU_texture_create_3d("volume_grid",
UNPACK3(dense_grid.resolution),
1,
format,
GPU_TEXTURE_USAGE_SHADER_READ,
dense_grid.voxels);
/* The texture can be null if the resolution along one axis is larger than
* GL_MAX_3D_TEXTURE_SIZE. */
if (cache_grid->texture != nullptr) {
GPU_texture_swizzle_set(cache_grid->texture, (channels == 3) ? "rgb1" : "rrr1");
GPU_texture_extend_mode(cache_grid->texture, GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
BKE_volume_dense_float_grid_clear(&dense_grid);
}
else {
MEM_delete(dense_grid.voxels);
printf("Error: Could not allocate 3D texture for volume.\n");
}
}
return cache_grid;
}
DRWVolumeGrid *DRW_volume_batch_cache_get_grid(Volume *volume,
const bke::VolumeGridData *volume_grid)
{
VolumeBatchCache *cache = volume_batch_cache_get(volume);
DRWVolumeGrid *grid = volume_grid_cache_get(volume, volume_grid, cache);
return (grid->texture != nullptr) ? grid : nullptr;
}
} // namespace blender::draw

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "GPU_batch.hh"
namespace blender {
/* Common */
// #define DRW_DEBUG_MESH_CACHE_REQUEST
#ifdef DRW_DEBUG_MESH_CACHE_REQUEST
# define DRW_ADD_FLAG_FROM_VBO_REQUEST(flag, vbo, value) \
(flag |= DRW_vbo_requested(vbo) ? (printf(" VBO requested " #vbo "\n") ? value : value) : 0)
# define DRW_ADD_FLAG_FROM_IBO_REQUEST(flag, ibo, value) \
(flag |= DRW_ibo_requested(ibo) ? (printf(" IBO requested " #ibo "\n") ? value : value) : 0)
#else
# define DRW_ADD_FLAG_FROM_VBO_REQUEST(flag, vbo, value) \
(flag |= DRW_vbo_requested(vbo) ? (value) : 0)
# define DRW_ADD_FLAG_FROM_IBO_REQUEST(flag, ibo, value) \
(flag |= DRW_ibo_requested(ibo) ? (value) : 0)
#endif
inline gpu::Batch *DRW_batch_request(gpu::Batch **batch)
{
/* XXX TODO(fclem): We are writing to batch cache here. Need to make this thread safe. */
if (*batch == nullptr) {
*batch = GPU_batch_calloc();
}
return *batch;
}
inline bool DRW_batch_requested(gpu::Batch *batch, GPUPrimType prim_type)
{
/* Batch has been requested if it has been created but not initialized. */
if (batch != nullptr && batch->verts[0] == nullptr) {
/* HACK. We init without a valid VBO and let the first vbo binding
* fill verts[0]. */
GPU_batch_init_ex(
batch, prim_type, reinterpret_cast<gpu::VertBuf *>(1), nullptr, GPUBatchFlag(0));
batch->verts[0] = nullptr;
return true;
}
return false;
}
inline void DRW_ibo_request(gpu::Batch *batch, gpu::IndexBuf **ibo)
{
if (*ibo == nullptr) {
*ibo = GPU_indexbuf_calloc();
}
if (batch != nullptr) {
GPU_batch_elembuf_set(batch, *ibo, false);
}
}
inline bool DRW_ibo_requested(gpu::IndexBuf *ibo)
{
/* TODO: do not rely on data uploaded. This prevents multi-threading.
* (need access to a GPU context). */
return (ibo != nullptr && !GPU_indexbuf_is_init(ibo));
}
inline void DRW_vbo_request(gpu::Batch *batch, gpu::VertBuf **vbo)
{
if (*vbo == nullptr) {
*vbo = GPU_vertbuf_calloc();
}
if (batch != nullptr) {
BLI_assert_msg(batch->procedural_vertices == -1,
"Request of vertex buffer for procedural batch is not valid operation.");
/* HACK we set VBO's that may not yet be valid. */
GPU_batch_vertbuf_add(batch, *vbo, false);
}
}
inline bool DRW_vbo_requested(gpu::VertBuf *vbo)
{
return (vbo != nullptr && (GPU_vertbuf_get_status(vbo) & GPU_VERTBUF_INIT) == 0);
}
} // namespace blender

View File

@@ -0,0 +1,164 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "GPU_viewport.hh"
#include "BLI_string_utf8.h"
#include "DRW_render.hh"
#include "DNA_space_types.h"
#include "DNA_view3d_types.h"
#include "BKE_colortools.hh"
#include "BKE_image.hh"
#include "BKE_scene.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_node_c.hh"
#include "draw_color_management.hh"
namespace blender::draw::color_management {
enum class eDRWColorManagementType {
ViewTransform = 0,
ViewTransformAndLook,
UseRenderSettings,
};
static float dither_get(eDRWColorManagementType color_management_type, const Scene &scene)
{
if (ELEM(color_management_type,
eDRWColorManagementType::ViewTransformAndLook,
eDRWColorManagementType::UseRenderSettings))
{
return scene.r.dither_intensity;
}
return 0.0f;
}
static eDRWColorManagementType drw_color_management_type_for_v3d(const Scene &scene,
const View3D &v3d)
{
const bool use_workbench = BKE_scene_uses_blender_workbench(&scene);
const bool use_scene_lights = V3D_USES_SCENE_LIGHTS(&v3d);
const bool use_scene_world = V3D_USES_SCENE_WORLD(&v3d);
if ((use_workbench && v3d.shading.type == OB_RENDER) || use_scene_lights || use_scene_world) {
return eDRWColorManagementType::UseRenderSettings;
}
if (v3d.shading.type >= OB_MATERIAL) {
return eDRWColorManagementType::ViewTransformAndLook;
}
return eDRWColorManagementType::ViewTransform;
}
static eDRWColorManagementType drw_color_management_type_for_space_image(const SpaceImage &sima)
{
const Image *image = sima.image;
/* Use inverse logic as there isn't a setting for `Color & Alpha`. */
const eSpaceImage_Flag display_channels_mode = static_cast<eSpaceImage_Flag>(sima.flag);
const bool display_color_channel = (display_channels_mode & (SI_SHOW_ALPHA | SI_SHOW_ZBUF)) == 0;
if (display_color_channel && image && (image->source != IMA_SRC_GENERATED) &&
((image->flag & IMA_VIEW_AS_RENDER) != 0))
{
return eDRWColorManagementType::UseRenderSettings;
}
return eDRWColorManagementType::ViewTransform;
}
static eDRWColorManagementType drw_color_management_type_for_space_node(Main &bmain,
const SpaceNode &snode)
{
if ((snode.flag & SNODE_BACKDRAW) && ED_node_is_compositor(&snode)) {
const Image *image = BKE_image_ensure_viewer(&bmain, IMA_TYPE_COMPOSITE, "Viewer Node");
if ((image->flag & IMA_VIEW_AS_RENDER) == 0) {
return eDRWColorManagementType::ViewTransform;
}
}
const eSpaceNode_Flag display_channels_mode = static_cast<eSpaceNode_Flag>(snode.flag);
const bool display_color_channel = (display_channels_mode & SNODE_SHOW_ALPHA) == 0;
if (display_color_channel) {
return eDRWColorManagementType::UseRenderSettings;
}
return eDRWColorManagementType::ViewTransform;
}
static eDRWColorManagementType drw_color_management_type_get(Main *bmain,
const Scene &scene,
const View3D *v3d,
const SpaceLink *space_data)
{
if (v3d) {
return drw_color_management_type_for_v3d(scene, *v3d);
}
if (space_data) {
switch (space_data->spacetype) {
case SPACE_IMAGE: {
const SpaceImage *sima = reinterpret_cast<const SpaceImage *>(space_data);
return drw_color_management_type_for_space_image(*sima);
}
case SPACE_NODE: {
const SpaceNode *snode = reinterpret_cast<const SpaceNode *>(space_data);
return drw_color_management_type_for_space_node(*bmain, *snode);
}
}
}
return eDRWColorManagementType::UseRenderSettings;
}
static void viewport_settings_apply(GPUViewport &viewport,
const Scene &scene,
const eDRWColorManagementType color_management_type)
{
const ColorManagedDisplaySettings *display_settings = &scene.display_settings;
ColorManagedViewSettings view_settings;
switch (color_management_type) {
case eDRWColorManagementType::ViewTransform: {
/* For workbench use only default view transform in configuration,
* using no scene settings. */
BKE_color_managed_view_settings_init(&view_settings, display_settings, nullptr);
break;
}
case eDRWColorManagementType::ViewTransformAndLook: {
/* Use only view transform + look and nothing else for lookdev without
* scene lighting, as exposure depends on scene light intensity. */
BKE_color_managed_view_settings_init(&view_settings, display_settings, nullptr);
STRNCPY_UTF8(view_settings.view_transform, scene.view_settings.view_transform);
STRNCPY_UTF8(view_settings.look, scene.view_settings.look);
break;
}
case eDRWColorManagementType::UseRenderSettings: {
/* Use full render settings, for renders with scene lighting. */
view_settings = scene.view_settings;
break;
}
}
const float dither = dither_get(color_management_type, scene);
GPU_viewport_colorspace_set(&viewport, &view_settings, display_settings, dither);
}
void viewport_color_management_set(GPUViewport &viewport, DRWContext &draw_ctx)
{
const Depsgraph *depsgraph = draw_ctx.depsgraph;
Main *bmain = DEG_get_bmain(depsgraph);
const eDRWColorManagementType color_management_type = drw_color_management_type_get(
bmain, *draw_ctx.scene, draw_ctx.v3d, draw_ctx.space_data);
viewport_settings_apply(viewport, *draw_ctx.scene, color_management_type);
}
} // namespace blender::draw::color_management

View File

@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
namespace blender {
namespace gpu {
class Texture;
}
struct GPUViewport;
struct DRWContext;
namespace draw::color_management {
void viewport_color_management_set(GPUViewport &viewport, DRWContext &draw_ctx);
} // namespace draw::color_management
} // namespace blender

View File

@@ -0,0 +1,884 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "GPU_compute.hh"
#include "GPU_debug.hh"
#include "draw_command.hh"
#include "draw_pass.hh"
#include "draw_shader.hh"
#include "draw_view.hh"
#include <bitset>
#include <sstream>
namespace blender::draw::command {
static gpu::Batch *procedural_batch_get(GPUPrimType primitive)
{
switch (primitive) {
case GPU_PRIM_POINTS:
return GPU_batch_procedural_points_get();
case GPU_PRIM_LINES:
return GPU_batch_procedural_lines_get();
case GPU_PRIM_TRIS:
return GPU_batch_procedural_triangles_get();
case GPU_PRIM_TRI_STRIP:
return GPU_batch_procedural_triangle_strips_get();
default:
/* Add new one as needed. */
BLI_assert_unreachable();
return nullptr;
}
}
/* -------------------------------------------------------------------- */
/** \name Commands Execution
* \{ */
void ShaderBind::execute(RecordingState &state) const
{
state.shader_use_specialization = !GPU_shader_get_default_constant_state(shader).is_empty();
if (assign_if_different(state.shader, shader) || state.shader_use_specialization) {
GPU_shader_bind(shader, state.specialization_constants_get());
}
/* Signal that we can reload the default for a different specialization later on.
* However, we keep the specialization_constants state around for compute shaders. */
state.specialization_constants_in_use = false;
}
void FramebufferBind::execute() const
{
GPU_framebuffer_bind(*framebuffer);
}
void SubPassTransition::execute() const
{
/* TODO(fclem): Require framebuffer bind to always be part of the pass so that we can track it
* inside RecordingState. */
gpu::FrameBuffer *framebuffer = GPU_framebuffer_active_get();
/* Unpack to the real enum type. */
const GPUAttachmentState states[9] = {
GPUAttachmentState(depth_state),
GPUAttachmentState(color_states[0]),
GPUAttachmentState(color_states[1]),
GPUAttachmentState(color_states[2]),
GPUAttachmentState(color_states[3]),
GPUAttachmentState(color_states[4]),
GPUAttachmentState(color_states[5]),
GPUAttachmentState(color_states[6]),
GPUAttachmentState(color_states[7]),
};
GPU_framebuffer_subpass_transition_array(framebuffer, states, ARRAY_SIZE(states));
}
void ResourceBind::execute() const
{
if (slot == -1) {
return;
}
switch (type) {
case ResourceBind::Type::Sampler:
GPU_texture_bind_ex(is_reference ? *texture_ref : texture, sampler, slot);
break;
case ResourceBind::Type::BufferSampler:
GPU_vertbuf_bind_as_texture(is_reference ? *vertex_buf_ref : vertex_buf, slot);
break;
case ResourceBind::Type::Image:
GPU_texture_image_bind(is_reference ? *texture_ref : texture, slot);
break;
case ResourceBind::Type::UniformBuf:
GPU_uniformbuf_bind(is_reference ? *uniform_buf_ref : uniform_buf, slot);
break;
case ResourceBind::Type::StorageBuf:
GPU_storagebuf_bind(is_reference ? *storage_buf_ref : storage_buf, slot);
break;
case ResourceBind::Type::UniformAsStorageBuf:
GPU_uniformbuf_bind_as_ssbo(is_reference ? *uniform_buf_ref : uniform_buf, slot);
break;
case ResourceBind::Type::VertexAsStorageBuf:
GPU_vertbuf_bind_as_ssbo(is_reference ? *vertex_buf_ref : vertex_buf, slot);
break;
case ResourceBind::Type::IndexAsStorageBuf:
GPU_indexbuf_bind_as_ssbo(is_reference ? *index_buf_ref : index_buf, slot);
break;
}
}
void PushConstant::execute(RecordingState &state) const
{
if (location == -1) {
return;
}
switch (type) {
case PushConstant::Type::IntValue:
GPU_shader_uniform_int_ex(state.shader, location, comp_len, array_len, int4_value);
break;
case PushConstant::Type::IntReference:
GPU_shader_uniform_int_ex(state.shader, location, comp_len, array_len, int_ref);
break;
case PushConstant::Type::FloatValue:
GPU_shader_uniform_float_ex(state.shader, location, comp_len, array_len, float4_value);
break;
case PushConstant::Type::FloatReference:
GPU_shader_uniform_float_ex(state.shader, location, comp_len, array_len, float_ref);
break;
}
}
void SpecializeConstant::execute(command::RecordingState &state) const
{
/* All specialization constants should exist as they are not optimized out like uniforms. */
BLI_assert(location != -1);
if (state.specialization_constants_in_use == false) {
state.specialization_constants = GPU_shader_get_default_constant_state(this->shader);
state.specialization_constants_in_use = true;
}
switch (type) {
case SpecializeConstant::Type::IntValue:
state.specialization_constants.set_value(location, int_value);
break;
case SpecializeConstant::Type::IntReference:
state.specialization_constants.set_value(location, *int_ref);
break;
case SpecializeConstant::Type::UintValue:
state.specialization_constants.set_value(location, uint_value);
break;
case SpecializeConstant::Type::UintReference:
state.specialization_constants.set_value(location, *uint_ref);
break;
case SpecializeConstant::Type::FloatValue:
state.specialization_constants.set_value(location, float_value);
break;
case SpecializeConstant::Type::FloatReference:
state.specialization_constants.set_value(location, *float_ref);
break;
case SpecializeConstant::Type::BoolValue:
state.specialization_constants.set_value(location, bool_value);
break;
case SpecializeConstant::Type::BoolReference:
state.specialization_constants.set_value(location, *bool_ref);
break;
}
}
void Draw::execute(RecordingState &state) const
{
state.front_facing_set(res_id.has_inverted_handedness());
/* Use same logic as in `finalize_commands`. */
uint instance_first = 0;
if (res_id.raw > 0) {
instance_first = state.instance_offset;
state.instance_offset += instance_len;
}
GPU_shader_get_default_constant_state(state.shader).is_empty();
if (is_primitive_expansion()) {
/* Expanded draw-call. */
IndexRange expanded_range = GPU_batch_draw_expanded_parameter_get(
batch->prim_type,
GPUPrimType(expand_prim_type),
vertex_len,
vertex_first,
expand_prim_len);
if (expanded_range.is_empty()) {
/* Nothing to draw, and can lead to asserts in GPU_batch_bind_as_resources. */
return;
}
GPU_batch_bind_as_resources(batch, state.shader, state.specialization_constants_get());
gpu::Batch *gpu_batch = procedural_batch_get(GPUPrimType(expand_prim_type));
GPU_batch_set_shader(gpu_batch, state.shader, state.specialization_constants_get());
GPU_batch_draw_advanced(
gpu_batch, expanded_range.start(), expanded_range.size(), instance_first, instance_len);
}
else {
/* Regular draw-call. */
GPU_batch_set_shader(batch, state.shader, state.specialization_constants_get());
GPU_batch_draw_advanced(batch, vertex_first, vertex_len, instance_first, instance_len);
}
}
void DrawMulti::execute(RecordingState &state) const
{
DrawMultiBuf::DrawCommandBuf &indirect_buf = multi_draw_buf->command_buf_;
DrawMultiBuf::DrawGroupBuf &groups = multi_draw_buf->group_buf_;
uint group_index = this->group_first;
while (group_index != uint(-1)) {
const DrawGroup &group = groups[group_index];
if (group.vertex_len > 0) {
gpu::Batch *batch = group.desc.gpu_batch;
if (GPUPrimType(group.desc.expand_prim_type) != GPU_PRIM_NONE) {
/* Bind original batch as resource and use a procedural batch to issue the draw-call. */
GPU_batch_bind_as_resources(
group.desc.gpu_batch, state.shader, state.specialization_constants_get());
batch = procedural_batch_get(GPUPrimType(group.desc.expand_prim_type));
}
GPU_batch_set_shader(batch, state.shader, state.specialization_constants_get());
constexpr intptr_t stride = sizeof(DrawCommand);
/* We have 2 indirect command reserved per draw group. */
intptr_t offset = stride * group_index * 2;
/* Draw negatively scaled geometry first. */
if (group.len - group.front_facing_len > 0) {
state.front_facing_set(true);
GPU_batch_draw_indirect(batch, indirect_buf, offset);
}
if (group.front_facing_len > 0) {
state.front_facing_set(false);
GPU_batch_draw_indirect(batch, indirect_buf, offset + stride);
}
}
group_index = group.next;
}
}
void DrawIndirect::execute(RecordingState &state) const
{
state.front_facing_set(res_id.has_inverted_handedness());
GPU_batch_draw_indirect(batch, *indirect_buf, 0);
}
void Dispatch::execute(RecordingState &state) const
{
if (is_reference) {
GPU_compute_dispatch(
state.shader, size_ref->x, size_ref->y, size_ref->z, state.specialization_constants_get());
}
else {
GPU_compute_dispatch(
state.shader, size.x, size.y, size.z, state.specialization_constants_get());
}
}
void DispatchIndirect::execute(RecordingState &state) const
{
GPU_compute_dispatch_indirect(state.shader, *indirect_buf, state.specialization_constants_get());
}
void Barrier::execute() const
{
GPU_memory_barrier(type);
}
void Clear::execute() const
{
gpu::FrameBuffer *fb = GPU_framebuffer_active_get();
GPU_framebuffer_clear(fb, GPUFrameBufferBits(clear_channels), double4(color), depth, stencil);
}
void ClearMulti::execute() const
{
gpu::FrameBuffer *fb = GPU_framebuffer_active_get();
GPU_framebuffer_multi_clear(fb, Span<double4>(colors, colors_len));
}
void StateSet::execute(RecordingState &recording_state) const
{
bool state_changed = assign_if_different(recording_state.pipeline_state, new_state);
bool clip_changed = assign_if_different(recording_state.clip_plane_count, clip_plane_count);
if (!state_changed && !clip_changed) {
return;
}
GPU_state_set(to_write_mask(new_state),
to_blend(new_state),
to_face_cull_test(new_state),
to_depth_test(new_state),
to_stencil_test(new_state),
to_stencil_op(new_state),
to_provoking_vertex(new_state));
if (new_state & DRW_STATE_CLIP_CONTROL_UNIT_RANGE) {
GPU_clip_control_unit_range(true);
}
else {
GPU_clip_control_unit_range(false);
}
/* TODO: this should be part of shader state. */
GPU_clip_distances(recording_state.clip_plane_count);
if (new_state & DRW_STATE_PROGRAM_POINT_SIZE) {
GPU_program_point_size(true);
}
else {
GPU_program_point_size(false);
}
}
void StateSet::set(DRWState state)
{
RecordingState recording_state;
StateSet{state, 0}.execute(recording_state);
/* This function is used for cleaning the state for the viewport drawing.
* Make sure to reset textures resources to avoid feedback loop when rendering (see #131652). */
GPU_texture_unbind_all();
GPU_texture_image_unbind_all();
GPU_uniformbuf_debug_unbind_all();
GPU_storagebuf_debug_unbind_all();
/* Remained of legacy draw manager. Kept it to avoid regression, but might become unneeded. */
GPU_point_size(5);
GPU_line_smooth(false);
GPU_line_width(0.0f);
}
void StencilSet::execute() const
{
GPU_stencil_write_mask_set(write_mask);
GPU_stencil_compare_mask_set(compare_mask);
GPU_stencil_reference_set(reference);
}
void TextureCopy::execute() const
{
gpu::Texture *exec_src = src_is_ref ? *src_ref : src;
gpu::Texture *exec_dst = dst_is_ref ? *dst_ref : dst;
if (!GPU_texture_is_view(exec_src) && !GPU_texture_is_view(exec_dst)) {
GPU_texture_copy(exec_dst, exec_src);
return;
}
/* WORKAROUND: There are some issues with copies involving texture views.
* Needed for the EEVEE Prepass depth copy. */
/* TODO(@pragma37): This path should be removed in 5.3 once #158607 lands. */
BLI_assert(GPU_texture_has_depth_format(exec_src));
gpu::FrameBuffer *src_fb = GPU_framebuffer_create("TextureCopy-tmp-src");
gpu::FrameBuffer *dst_fb = GPU_framebuffer_create("TextureCopy-tmp-dst");
GPU_framebuffer_ensure_config(&src_fb, {GPU_ATTACHMENT_TEXTURE(exec_src)});
GPU_framebuffer_ensure_config(&dst_fb, {GPU_ATTACHMENT_TEXTURE(exec_dst)});
GPU_framebuffer_blit(src_fb, 0, dst_fb, 0, GPUFrameBufferBits::GPU_DEPTH_BIT);
GPU_framebuffer_free(src_fb);
GPU_framebuffer_free(dst_fb);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Commands Serialization for debugging
* \{ */
std::string ShaderBind::serialize() const
{
return std::string(".shader_bind(") + GPU_shader_get_name(shader) + ")";
}
std::string FramebufferBind::serialize() const
{
return std::string(".framebuffer_bind(") +
(*framebuffer == nullptr ? "nullptr" : GPU_framebuffer_get_name(*framebuffer)) + ")";
}
std::string SubPassTransition::serialize() const
{
auto to_str = [](GPUAttachmentState state) {
return (state != GPU_ATTACHMENT_IGNORE) ?
((state == GPU_ATTACHMENT_WRITE) ? "write" : "read") :
"ignore";
};
return std::string(".subpass_transition(\n") +
"depth=" + to_str(GPUAttachmentState(depth_state)) + ",\n" +
"color0=" + to_str(GPUAttachmentState(color_states[0])) + ",\n" +
"color1=" + to_str(GPUAttachmentState(color_states[1])) + ",\n" +
"color2=" + to_str(GPUAttachmentState(color_states[2])) + ",\n" +
"color3=" + to_str(GPUAttachmentState(color_states[3])) + ",\n" +
"color4=" + to_str(GPUAttachmentState(color_states[4])) + ",\n" +
"color5=" + to_str(GPUAttachmentState(color_states[5])) + ",\n" +
"color6=" + to_str(GPUAttachmentState(color_states[6])) + ",\n" +
"color7=" + to_str(GPUAttachmentState(color_states[7])) + "\n)";
}
std::string ResourceBind::serialize() const
{
switch (type) {
case Type::Sampler:
return std::string(".bind_texture") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ", sampler=" + sampler.to_string() + ")";
case Type::BufferSampler:
return std::string(".bind_vertbuf_as_texture") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::Image:
return std::string(".bind_image") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::UniformBuf:
return std::string(".bind_uniform_buf") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::StorageBuf:
return std::string(".bind_storage_buf") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::UniformAsStorageBuf:
return std::string(".bind_uniform_as_ssbo") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::VertexAsStorageBuf:
return std::string(".bind_vertbuf_as_ssbo") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
case Type::IndexAsStorageBuf:
return std::string(".bind_indexbuf_as_ssbo") + (is_reference ? "_ref" : "") + "(" +
std::to_string(slot) + ")";
default:
BLI_assert_unreachable();
return "";
}
}
std::string PushConstant::serialize() const
{
std::stringstream ss;
for (int i = 0; i < array_len; i++) {
switch (comp_len) {
case 1:
switch (type) {
case Type::IntValue:
ss << int1_value;
break;
case Type::IntReference:
ss << int_ref[i];
break;
case Type::FloatValue:
ss << float1_value;
break;
case Type::FloatReference:
ss << float_ref[i];
break;
}
break;
case 2:
switch (type) {
case Type::IntValue:
ss << int2_value;
break;
case Type::IntReference:
ss << int2_ref[i];
break;
case Type::FloatValue:
ss << float2_value;
break;
case Type::FloatReference:
ss << float2_ref[i];
break;
}
break;
case 3:
switch (type) {
case Type::IntValue:
ss << int3_value;
break;
case Type::IntReference:
ss << int3_ref[i];
break;
case Type::FloatValue:
ss << float3_value;
break;
case Type::FloatReference:
ss << float3_ref[i];
break;
}
break;
case 4:
switch (type) {
case Type::IntValue:
ss << int4_value;
break;
case Type::IntReference:
ss << int4_ref[i];
break;
case Type::FloatValue:
ss << float4_value;
break;
case Type::FloatReference:
ss << float4_ref[i];
break;
}
break;
case 16:
switch (type) {
case Type::IntValue:
case Type::IntReference:
BLI_assert_unreachable();
break;
case Type::FloatValue:
ss << float4x4(
(&float4_value)[0], (&float4_value)[1], (&float4_value)[2], (&float4_value)[3]);
break;
case Type::FloatReference:
ss << *float4x4_ref;
break;
}
break;
}
if (i < array_len - 1) {
ss << ", ";
}
}
return std::string(".push_constant(") + std::to_string(location) + ", data=" + ss.str() + ")";
}
std::string SpecializeConstant::serialize() const
{
std::stringstream ss;
switch (type) {
case Type::IntValue:
ss << int_value;
break;
case Type::UintValue:
ss << uint_value;
break;
case Type::FloatValue:
ss << float_value;
break;
case Type::BoolValue:
ss << bool_value;
break;
case Type::IntReference:
ss << *int_ref;
break;
case Type::UintReference:
ss << *uint_ref;
break;
case Type::FloatReference:
ss << *float_ref;
break;
case Type::BoolReference:
ss << *bool_ref;
break;
}
return std::string(".specialize_constant(") + std::to_string(location) + ", data=" + ss.str() +
")";
}
std::string Draw::serialize() const
{
std::string inst_len = std::to_string(instance_len);
std::string vert_len = (vertex_len == uint(-1)) ? "from_batch" : std::to_string(vertex_len);
std::string vert_first = (vertex_first == uint(-1)) ? "from_batch" :
std::to_string(vertex_first);
return std::string(".draw(inst_len=") + inst_len + ", vert_len=" + vert_len +
", vert_first=" + vert_first + ", res_id=" + std::to_string(res_id.index()) + ")";
}
std::string DrawMulti::serialize(const std::string &line_prefix) const
{
DrawMultiBuf::DrawGroupBuf &groups = multi_draw_buf->group_buf_;
MutableSpan<DrawPrototype> prototypes(multi_draw_buf->prototype_buf_.data(),
multi_draw_buf->prototype_count_);
/* This emulates the GPU sorting but without the unstable draw order. */
std::ranges::sort(prototypes, [](const DrawPrototype &a, const DrawPrototype &b) {
return (a.group_id < b.group_id) || (a.group_id == b.group_id && a.res_id > b.res_id);
});
/* Compute prefix sum to have correct offsets. */
uint prefix_sum = 0u;
for (DrawGroup &group : groups) {
group.start = prefix_sum;
prefix_sum += group.front_facing_counter + group.back_facing_counter;
}
std::stringstream ss;
uint group_len = 0;
uint group_index = this->group_first;
while (group_index != uint(-1)) {
const DrawGroup &grp = groups[group_index];
ss << std::endl << line_prefix << " .group(id=" << group_index << ", len=" << grp.len << ")";
intptr_t offset = grp.start;
if (grp.back_facing_counter > 0) {
for (DrawPrototype &proto : prototypes.slice_safe({offset, grp.back_facing_counter})) {
BLI_assert(proto.group_id == group_index);
ResourceID res_id(proto.res_id);
BLI_assert(res_id.has_inverted_handedness());
ss << std::endl
<< line_prefix << " .proto(instance_len=" << std::to_string(proto.instance_len)
<< ", resource_id=" << std::to_string(res_id.index()) << ", back_face)";
}
offset += grp.back_facing_counter;
}
if (grp.front_facing_counter > 0) {
for (DrawPrototype &proto : prototypes.slice_safe({offset, grp.front_facing_counter})) {
BLI_assert(proto.group_id == group_index);
ResourceID res_id(proto.res_id);
BLI_assert(!res_id.has_inverted_handedness());
ss << std::endl
<< line_prefix << " .proto(instance_len=" << std::to_string(proto.instance_len)
<< ", resource_id=" << std::to_string(res_id.index()) << ", front_face)";
}
}
group_index = grp.next;
group_len++;
}
ss << std::endl;
return line_prefix + ".draw_multi(" + std::to_string(group_len) + ")" + ss.str();
}
std::string DrawIndirect::serialize() const
{
return std::string(".draw_indirect()");
}
std::string Dispatch::serialize() const
{
int3 sz = is_reference ? *size_ref : size;
return std::string(".dispatch") + (is_reference ? "_ref" : "") + "(" + std::to_string(sz.x) +
", " + std::to_string(sz.y) + ", " + std::to_string(sz.z) + ")";
}
std::string DispatchIndirect::serialize() const
{
return std::string(".dispatch_indirect()");
}
std::string Barrier::serialize() const
{
/* TODO(@fclem): Better serialization... */
return std::string(".barrier(") + std::to_string(type) + ")";
}
std::string Clear::serialize() const
{
std::stringstream ss;
if (GPUFrameBufferBits(clear_channels) & GPU_COLOR_BIT) {
ss << "color=" << color;
if (GPUFrameBufferBits(clear_channels) & (GPU_DEPTH_BIT | GPU_STENCIL_BIT)) {
ss << ", ";
}
}
if (GPUFrameBufferBits(clear_channels) & GPU_DEPTH_BIT) {
ss << "depth=" << depth;
if (GPUFrameBufferBits(clear_channels) & GPU_STENCIL_BIT) {
ss << ", ";
}
}
if (GPUFrameBufferBits(clear_channels) & GPU_STENCIL_BIT) {
ss << "stencil=0b" << std::bitset<8>(stencil) << ")";
}
return std::string(".clear(") + ss.str() + ")";
}
std::string ClearMulti::serialize() const
{
std::stringstream ss;
for (double4 color : Span<double4>(colors, colors_len)) {
ss << color << ", ";
}
return std::string(".clear_multi(colors={") + ss.str() + "})";
}
std::string StateSet::serialize() const
{
/* TODO(@fclem): Better serialization... */
return std::string(".state_set(") + std::to_string(new_state) + ")";
}
std::string StencilSet::serialize() const
{
std::stringstream ss;
ss << ".stencil_set(write_mask=0b" << std::bitset<8>(write_mask) << ", reference=0b"
<< std::bitset<8>(reference) << ", compare_mask=0b" << std::bitset<8>(compare_mask) << ")";
return ss.str();
}
std::string TextureCopy::serialize() const
{
return ".texture_copy()";
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Commands buffers binding / command / resource ID generation
* \{ */
void DrawCommandBuf::finalize_commands(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
SubPassVector &sub_passes,
uint &resource_id_count,
ResourceIdBuf &resource_id_buf)
{
for (const Header &header : headers) {
if (header.type == Type::SubPass) {
/** WARNING: Recursive. */
auto &sub = sub_passes[int64_t(header.index)];
finalize_commands(
sub.headers_, sub.commands_, sub_passes, resource_id_count, resource_id_buf);
}
if (header.type != Type::Draw) {
continue;
}
Draw &cmd = commands[header.index].draw;
int batch_vert_len, batch_vert_first, batch_base_index, batch_inst_len;
/* Now that GPUBatches are guaranteed to be finished, extract their parameters. */
GPU_batch_draw_parameter_get(
cmd.batch, &batch_vert_len, &batch_vert_first, &batch_base_index, &batch_inst_len);
/* Instancing attributes are not supported using the new pipeline since we use the base
* instance to set the correct resource_id. Workaround is a storage_buf + gl_InstanceID. */
BLI_assert(batch_inst_len == 1);
if (cmd.vertex_len == uint(-1)) {
cmd.vertex_len = batch_vert_len;
}
/* NOTE: Only do this if a handle is present. If a draw-call is using instancing with null
* handle, the shader should not rely on `resource_id` at ***all***. This allows procedural
* instanced draw-calls with lots of instances with no overhead. */
/* TODO(fclem): Think about either fixing this feature or removing support for instancing all
* together. */
if (cmd.res_id.raw > 0) {
/* Save correct offset to start of resource_id buffer region for this draw. */
uint instance_first = resource_id_count;
resource_id_count += cmd.instance_len;
/* Ensure the buffer is big enough. */
resource_id_buf.get_or_resize(resource_id_count - 1);
/* Copy the resource id for all instances. */
uint index = cmd.res_id.index();
for (int i = instance_first; i < (instance_first + cmd.instance_len); i++) {
resource_id_buf[i] = index;
}
}
}
}
void DrawCommandBuf::generate_commands(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
SubPassVector &sub_passes)
{
/* First instance ID contains the null handle with identity transform.
* This is referenced for draw-calls with no handle. */
resource_id_buf_.get_or_resize(0) = 0;
resource_id_count_ = 1;
finalize_commands(headers, commands, sub_passes, resource_id_count_, resource_id_buf_);
resource_id_buf_.push_update();
}
void DrawCommandBuf::bind(RecordingState & /*state*/)
{
GPU_storagebuf_bind(resource_id_buf_, DRW_RESOURCE_ID_SLOT);
}
void DrawMultiBuf::generate_commands(Vector<Header, 0> & /*headers*/,
Vector<Undetermined, 0> & /*commands*/,
VisibilityBuf &visibility_buf,
int visibility_word_per_draw,
int view_len,
bool use_custom_ids)
{
GPU_debug_group_begin("DrawMultiBuf.bind");
resource_id_count_ = 0u;
for (DrawGroup &group : MutableSpan<DrawGroup>(group_buf_.data(), group_count_)) {
/* Compute prefix sum of all instance of previous group. */
group.start = resource_id_count_;
resource_id_count_ += group.len;
int batch_vert_len, batch_vert_first, batch_base_index, batch_inst_len;
/* Now that GPUBatches are guaranteed to be finished, extract their parameters. */
GPU_batch_draw_parameter_get(group.desc.gpu_batch,
&batch_vert_len,
&batch_vert_first,
&batch_base_index,
&batch_inst_len);
group.vertex_len = group.desc.vertex_len == 0 ? batch_vert_len : group.desc.vertex_len;
group.vertex_first = group.desc.vertex_first == -1 ? batch_vert_first :
group.desc.vertex_first;
group.base_index = batch_base_index;
/* Instancing attributes are not supported using the new pipeline since we use the base
* instance to set the correct resource_id. Workaround is a storage_buf + gl_InstanceID. */
BLI_assert(batch_inst_len == 1);
UNUSED_VARS_NDEBUG(batch_inst_len);
if (group.desc.expand_prim_type != GPU_PRIM_NONE) {
/* Expanded draw-call. */
IndexRange vert_range = GPU_batch_draw_expanded_parameter_get(
group.desc.gpu_batch->prim_type,
GPUPrimType(group.desc.expand_prim_type),
group.vertex_len,
group.vertex_first,
group.desc.expand_prim_len);
group.vertex_first = vert_range.start();
group.vertex_len = vert_range.size();
/* Override base index to -1 as the generated draw-call will not use an index buffer and do
* the indirection manually inside the shader. */
group.base_index = -1;
}
/* Reset counters to 0 for the GPU. */
group.total_counter = group.front_facing_counter = group.back_facing_counter = 0;
}
group_buf_.push_update();
prototype_buf_.push_update();
/* Allocate enough for the expansion pass. */
resource_id_buf_.get_or_resize(resource_id_count_ * view_len * (use_custom_ids ? 2 : 1));
/* Two commands per group (inverted and non-inverted scale). */
command_buf_.get_or_resize(group_count_ * 2);
if (prototype_count_ > 0) {
gpu::Shader *shader = DRW_shader_draw_command_generate_get();
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "prototype_len", prototype_count_);
GPU_shader_uniform_1i(shader, "visibility_word_per_draw", visibility_word_per_draw);
GPU_shader_uniform_1i(shader, "view_len", view_len);
GPU_shader_uniform_1i(shader, "view_shift", log2_ceil_u(view_len));
GPU_shader_uniform_1b(shader, "use_custom_ids", use_custom_ids);
GPU_storagebuf_bind(group_buf_, GPU_shader_get_ssbo_binding(shader, "group_buf"));
GPU_storagebuf_bind(visibility_buf, GPU_shader_get_ssbo_binding(shader, "visibility_buf"));
GPU_storagebuf_bind(prototype_buf_, GPU_shader_get_ssbo_binding(shader, "prototype_buf"));
GPU_storagebuf_bind(command_buf_, GPU_shader_get_ssbo_binding(shader, "command_buf"));
GPU_storagebuf_bind(resource_id_buf_, DRW_RESOURCE_ID_SLOT);
GPU_compute_dispatch(shader, divide_ceil_u(prototype_count_, DRW_COMMAND_GROUP_SIZE), 1, 1);
/* TODO(@fclem): Investigate moving the barrier in the bind function. */
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
GPU_storagebuf_sync_as_indirect_buffer(command_buf_);
}
GPU_debug_group_end();
}
void DrawMultiBuf::bind(RecordingState & /*state*/)
{
GPU_storagebuf_bind(resource_id_buf_, DRW_RESOURCE_ID_SLOT);
}
/** \} */
}; // namespace blender::draw::command

View File

@@ -0,0 +1,783 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup draw
*
* Commands stored inside draw passes. Converted into GPU commands upon pass submission.
*
* Draw calls (primitive rendering commands) are managed by either `DrawCommandBuf` or
* `DrawMultiBuf`. See implementation details at their definition.
*/
#include "BKE_global.hh"
#include "BLI_map.hh"
#include "BLI_math_base.h"
#include "DRW_gpu_wrapper.hh"
#include "GPU_index_buffer.hh"
#include "draw_command_shared.hh"
#include "draw_handle.hh"
#include "draw_state.hh"
#include "draw_view.hh"
namespace blender {
/* Forward declarations. */
namespace draw::detail {
template<typename T, int64_t block_size> class SubPassVector;
template<typename DrawCommandBufType> class PassBase;
} // namespace draw::detail
namespace draw::command {
class DrawCommandBuf;
class DrawMultiBuf;
/* -------------------------------------------------------------------- */
/** \name Recording State
* \{ */
/**
* Command recording state.
* Keep track of several states and avoid redundant state changes.
*/
struct RecordingState {
gpu::shader::SpecializationConstants specialization_constants;
/* True if specialization_constants was set. */
bool specialization_constants_in_use = false;
/* True if the bound shader uses specialization. */
bool shader_use_specialization = false;
gpu::Shader *shader = nullptr;
bool front_facing = true;
bool inverted_view = false;
DRWState pipeline_state = DRW_STATE_NO_DRAW;
int clip_plane_count = 0;
/** Used for pass simple resource ID. Starts at 1 as 0 is the identity handle. */
int instance_offset = 1;
void front_facing_set(bool facing)
{
/* Facing is inverted if view is not in expected handedness. */
facing = this->inverted_view == facing;
/* Remove redundant changes. */
if (assign_if_different(this->front_facing, facing)) {
GPU_front_facing(!facing);
}
}
void cleanup()
{
if (front_facing == false) {
GPU_front_facing(false);
}
if (G.debug & G_DEBUG_GPU) {
GPU_storagebuf_debug_unbind_all();
GPU_texture_image_unbind_all();
GPU_texture_unbind_all();
GPU_uniformbuf_debug_unbind_all();
}
}
const gpu::shader::SpecializationConstants *specialization_constants_get()
{
return shader_use_specialization ? &specialization_constants : nullptr;
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Regular Commands
* \{ */
enum class Type : uint8_t {
/**
* None Type commands are either uninitialized or are repurposed as data storage.
* They are skipped during submission.
*/
None = 0,
/** Commands stored as Undetermined in regular command buffer. */
Barrier,
Clear,
ClearMulti,
Dispatch,
DispatchIndirect,
Draw,
DrawIndirect,
FramebufferBind,
PushConstant,
SpecializeConstant,
ResourceBind,
ShaderBind,
SubPassTransition,
StateSet,
StencilSet,
TextureCopy,
/** Special commands stored in separate buffers. */
SubPass,
DrawMulti,
};
/**
* The index of the group is implicit since it is known by the one who want to
* access it. This also allows to have an indexed object to split the command
* stream.
*/
struct Header {
/** Command type. */
Type type;
/** Command index in command heap of this type. */
uint index;
};
struct ShaderBind {
gpu::Shader *shader;
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct FramebufferBind {
gpu::FrameBuffer **framebuffer;
void execute() const;
std::string serialize() const;
};
struct SubPassTransition {
/** \note uint8_t storing `GPUAttachmentState` for compactness. */
uint8_t depth_state;
/** \note 8 is GPU_FB_MAX_COLOR_ATTACHMENT. */
uint8_t color_states[8];
void execute() const;
std::string serialize() const;
};
struct ResourceBind {
GPUSamplerState sampler;
int slot;
bool is_reference;
enum class Type : uint8_t {
Sampler = 0,
BufferSampler,
Image,
UniformBuf,
StorageBuf,
UniformAsStorageBuf,
VertexAsStorageBuf,
IndexAsStorageBuf,
} type;
union {
/** TODO: Use draw::Texture|StorageBuffer|UniformBuffer as resources as they will give more
* debug info. */
gpu::UniformBuf *uniform_buf;
gpu::UniformBuf **uniform_buf_ref;
gpu::StorageBuf *storage_buf;
gpu::StorageBuf **storage_buf_ref;
/** NOTE: Texture is used for both Sampler and Image binds. */
gpu::Texture *texture;
gpu::Texture **texture_ref;
gpu::VertBuf *vertex_buf;
gpu::VertBuf **vertex_buf_ref;
gpu::IndexBuf *index_buf;
gpu::IndexBuf **index_buf_ref;
};
ResourceBind() = default;
ResourceBind(int slot_, gpu::UniformBuf *res)
: slot(slot_), is_reference(false), type(Type::UniformBuf), uniform_buf(res) {};
ResourceBind(int slot_, gpu::UniformBuf **res)
: slot(slot_), is_reference(true), type(Type::UniformBuf), uniform_buf_ref(res) {};
ResourceBind(int slot_, gpu::StorageBuf *res)
: slot(slot_), is_reference(false), type(Type::StorageBuf), storage_buf(res) {};
ResourceBind(int slot_, gpu::StorageBuf **res)
: slot(slot_), is_reference(true), type(Type::StorageBuf), storage_buf_ref(res) {};
ResourceBind(int slot_, gpu::UniformBuf *res, Type /*type*/)
: slot(slot_), is_reference(false), type(Type::UniformAsStorageBuf), uniform_buf(res) {};
ResourceBind(int slot_, gpu::UniformBuf **res, Type /*type*/)
: slot(slot_), is_reference(true), type(Type::UniformAsStorageBuf), uniform_buf_ref(res) {};
ResourceBind(int slot_, gpu::VertBuf *res, Type /*type*/)
: slot(slot_), is_reference(false), type(Type::VertexAsStorageBuf), vertex_buf(res) {};
ResourceBind(int slot_, gpu::VertBuf **res, Type /*type*/)
: slot(slot_), is_reference(true), type(Type::VertexAsStorageBuf), vertex_buf_ref(res) {};
ResourceBind(int slot_, gpu::IndexBuf *res, Type /*type*/)
: slot(slot_), is_reference(false), type(Type::IndexAsStorageBuf), index_buf(res) {};
ResourceBind(int slot_, gpu::IndexBuf **res, Type /*type*/)
: slot(slot_), is_reference(true), type(Type::IndexAsStorageBuf), index_buf_ref(res) {};
ResourceBind(int slot_, draw::Image *res)
: slot(slot_), is_reference(false), type(Type::Image), texture(draw::as_texture(res)) {};
ResourceBind(int slot_, draw::Image **res)
: slot(slot_), is_reference(true), type(Type::Image), texture_ref(draw::as_texture(res)) {};
ResourceBind(int slot_, gpu::Texture *res, GPUSamplerState state)
: sampler(state), slot(slot_), is_reference(false), type(Type::Sampler), texture(res) {};
ResourceBind(int slot_, gpu::Texture **res, GPUSamplerState state)
: sampler(state), slot(slot_), is_reference(true), type(Type::Sampler), texture_ref(res) {};
ResourceBind(int slot_, gpu::VertBuf *res)
: slot(slot_), is_reference(false), type(Type::BufferSampler), vertex_buf(res) {};
ResourceBind(int slot_, gpu::VertBuf **res)
: slot(slot_), is_reference(true), type(Type::BufferSampler), vertex_buf_ref(res) {};
void execute() const;
std::string serialize() const;
};
struct PushConstant {
int location;
uint8_t array_len;
uint8_t comp_len;
enum class Type : uint8_t {
IntValue = 0,
FloatValue,
IntReference,
FloatReference,
} type;
/**
* IMPORTANT: Data is at the end of the struct as it can span over the next commands.
* These next commands are not real commands but just memory to hold the data and are not
* referenced by any Command::Header.
* This is a hack to support float4x4 copy.
*/
union {
int int1_value;
int2 int2_value;
int3 int3_value;
int4 int4_value;
float float1_value;
float2 float2_value;
float3 float3_value;
float4 float4_value;
const int *int_ref;
const int2 *int2_ref;
const int3 *int3_ref;
const int4 *int4_ref;
const float *float_ref;
const float2 *float2_ref;
const float3 *float3_ref;
const float4 *float4_ref;
const float4x4 *float4x4_ref;
};
PushConstant() = default;
PushConstant(int loc, const float &val)
: location(loc), array_len(1), comp_len(1), type(Type::FloatValue), float1_value(val) {};
PushConstant(int loc, const float2 &val)
: location(loc), array_len(1), comp_len(2), type(Type::FloatValue), float2_value(val) {};
PushConstant(int loc, const float3 &val)
: location(loc), array_len(1), comp_len(3), type(Type::FloatValue), float3_value(val) {};
PushConstant(int loc, const float4 &val)
: location(loc), array_len(1), comp_len(4), type(Type::FloatValue), float4_value(val) {};
PushConstant(int loc, const int &val)
: location(loc), array_len(1), comp_len(1), type(Type::IntValue), int1_value(val) {};
PushConstant(int loc, const int2 &val)
: location(loc), array_len(1), comp_len(2), type(Type::IntValue), int2_value(val) {};
PushConstant(int loc, const int3 &val)
: location(loc), array_len(1), comp_len(3), type(Type::IntValue), int3_value(val) {};
PushConstant(int loc, const int4 &val)
: location(loc), array_len(1), comp_len(4), type(Type::IntValue), int4_value(val) {};
PushConstant(int loc, const float *val, int arr)
: location(loc), array_len(arr), comp_len(1), type(Type::FloatReference), float_ref(val) {};
PushConstant(int loc, const float2 *val, int arr)
: location(loc), array_len(arr), comp_len(2), type(Type::FloatReference), float2_ref(val) {};
PushConstant(int loc, const float3 *val, int arr)
: location(loc), array_len(arr), comp_len(3), type(Type::FloatReference), float3_ref(val) {};
PushConstant(int loc, const float4 *val, int arr)
: location(loc), array_len(arr), comp_len(4), type(Type::FloatReference), float4_ref(val) {};
PushConstant(int loc, const float4x4 *val)
: location(loc), array_len(1), comp_len(16), type(Type::FloatReference), float4x4_ref(val) {
};
PushConstant(int loc, const int *val, int arr)
: location(loc), array_len(arr), comp_len(1), type(Type::IntReference), int_ref(val) {};
PushConstant(int loc, const int2 *val, int arr)
: location(loc), array_len(arr), comp_len(2), type(Type::IntReference), int2_ref(val) {};
PushConstant(int loc, const int3 *val, int arr)
: location(loc), array_len(arr), comp_len(3), type(Type::IntReference), int3_ref(val) {};
PushConstant(int loc, const int4 *val, int arr)
: location(loc), array_len(arr), comp_len(4), type(Type::IntReference), int4_ref(val) {};
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct SpecializeConstant {
/* Shader to set the constant in. */
gpu::Shader *shader;
/* Value of the constant or a reference to it. */
union {
int int_value;
uint uint_value;
float float_value;
bool bool_value;
const int *int_ref;
const uint *uint_ref;
const float *float_ref;
const bool *bool_ref;
};
int location;
enum class Type : uint8_t {
IntValue = 0,
UintValue,
FloatValue,
BoolValue,
IntReference,
UintReference,
FloatReference,
BoolReference,
} type;
SpecializeConstant() = default;
SpecializeConstant(gpu::Shader *sh, int loc, const float &val)
: shader(sh), float_value(val), location(loc), type(Type::FloatValue) {};
SpecializeConstant(gpu::Shader *sh, int loc, const int &val)
: shader(sh), int_value(val), location(loc), type(Type::IntValue) {};
SpecializeConstant(gpu::Shader *sh, int loc, const uint &val)
: shader(sh), uint_value(val), location(loc), type(Type::UintValue) {};
SpecializeConstant(gpu::Shader *sh, int loc, const bool &val)
: shader(sh), bool_value(val), location(loc), type(Type::BoolValue) {};
SpecializeConstant(gpu::Shader *sh, int loc, const float *val)
: shader(sh), float_ref(val), location(loc), type(Type::FloatReference) {};
SpecializeConstant(gpu::Shader *sh, int loc, const int *val)
: shader(sh), int_ref(val), location(loc), type(Type::IntReference) {};
SpecializeConstant(gpu::Shader *sh, int loc, const uint *val)
: shader(sh), uint_ref(val), location(loc), type(Type::UintReference) {};
SpecializeConstant(gpu::Shader *sh, int loc, const bool *val)
: shader(sh), bool_ref(val), location(loc), type(Type::BoolReference) {};
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct Draw {
gpu::Batch *batch;
uint32_t instance_len : 24;
uint32_t expand_prim_type : 4; /* #GPUPrimType */
uint32_t expand_prim_len : 4;
uint32_t vertex_first;
uint32_t vertex_len;
ResourceID res_id;
Draw() = default;
Draw(gpu::Batch *batch,
uint instance_len,
uint vertex_len,
uint vertex_first,
GPUPrimType expanded_prim_type,
uint expanded_prim_len,
ResourceID res_id)
{
BLI_assert(batch != nullptr);
BLI_assert(expanded_prim_type <= 15);
BLI_assert(expanded_prim_len <= 15);
this->batch = batch;
this->res_id = res_id;
this->instance_len = min_uu(instance_len, (1 << 24) - 1);
this->vertex_len = vertex_len;
this->vertex_first = vertex_first;
this->expand_prim_type = expanded_prim_type;
this->expand_prim_len = expanded_prim_len;
}
bool is_primitive_expansion() const
{
return expand_prim_type != GPU_PRIM_NONE;
}
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct DrawMulti {
gpu::Batch *batch;
DrawMultiBuf *multi_draw_buf;
uint group_first;
uint uuid;
void execute(RecordingState &state) const;
std::string serialize(const std::string &line_prefix) const;
};
struct DrawIndirect {
gpu::Batch *batch;
gpu::StorageBuf **indirect_buf;
ResourceID res_id;
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct Dispatch {
bool is_reference;
union {
int3 size;
int3 *size_ref;
};
Dispatch() = default;
Dispatch(int3 group_len) : is_reference(false), size(group_len) {};
Dispatch(int3 *group_len) : is_reference(true), size_ref(group_len) {};
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct DispatchIndirect {
gpu::StorageBuf **indirect_buf;
void execute(RecordingState &state) const;
std::string serialize() const;
};
struct Barrier {
GPUBarrier type;
void execute() const;
std::string serialize() const;
};
struct Clear {
uint8_t clear_channels; /* #GPUFrameBufferBits. But want to save some bits. */
uint8_t stencil;
float depth;
float4 color;
void execute() const;
std::string serialize() const;
};
struct ClearMulti {
/** \note This should be a Span<float4> but we need have to only have trivial types here. */
const double4 *colors;
int colors_len;
void execute() const;
std::string serialize() const;
};
struct StateSet {
DRWState new_state;
int clip_plane_count;
void execute(RecordingState &state) const;
std::string serialize() const;
/* Set state of the GPU module manually. */
static void set(DRWState state = DRW_STATE_DEFAULT);
};
struct StencilSet {
uint write_mask;
uint compare_mask;
uint reference;
void execute() const;
std::string serialize() const;
};
struct TextureCopy {
union {
gpu::Texture *src;
gpu::Texture **src_ref;
};
union {
gpu::Texture *dst;
gpu::Texture **dst_ref;
};
bool src_is_ref;
bool dst_is_ref;
void execute() const;
std::string serialize() const;
};
union Undetermined {
ShaderBind shader_bind;
ResourceBind resource_bind;
FramebufferBind framebuffer_bind;
SubPassTransition subpass_transition;
PushConstant push_constant;
SpecializeConstant specialize_constant;
Draw draw;
DrawMulti draw_multi;
DrawIndirect draw_indirect;
Dispatch dispatch;
DispatchIndirect dispatch_indirect;
Barrier barrier;
Clear clear;
ClearMulti clear_multi;
StateSet state_set;
StencilSet stencil_set;
TextureCopy texture_copy;
};
/** Try to keep the command size as low as possible for performance. */
BLI_STATIC_ASSERT(sizeof(Undetermined) <= 24, "One of the command type is too large.")
/** \} */
/* -------------------------------------------------------------------- */
/** \name Draw Commands
*
* A draw command buffer used to issue single draw commands without instance merging or any
* other optimizations.
*
* It still uses a ResourceIdBuf to keep the same shader interface as multi draw commands.
*
* \{ */
class DrawCommandBuf {
friend Manager;
private:
using ResourceIdBuf = StorageArrayBuffer<uint, 128, false>;
using SubPassVector = detail::SubPassVector<detail::PassBase<DrawCommandBuf>, 16>;
/** Array of resource id. One per instance. Generated on GPU and send to GPU. */
ResourceIdBuf resource_id_buf_;
/** Used items in the resource_id_buf_. Not it's allocated length. */
uint resource_id_count_ = 0;
public:
void clear()
{
resource_id_buf_.trim_to_next_power_of_2(resource_id_count_);
};
void append_draw(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
gpu::Batch *batch,
uint instance_len,
uint vertex_len,
uint vertex_first,
ResourceIDRange id_range,
uint custom_id,
GPUPrimType expanded_prim_type,
uint16_t expanded_prim_len)
{
BLI_assert(batch != nullptr);
vertex_first = vertex_first != -1 ? vertex_first : 0;
instance_len = instance_len != -1 ? instance_len : 1;
BLI_assert_msg(custom_id == 0, "Custom ID is not supported in PassSimple");
UNUSED_VARS_NDEBUG(custom_id);
for (auto res_id : id_range.id_range()) {
int64_t index = commands.append_and_get_index({});
headers.append({Type::Draw, uint(index)});
commands[index].draw = {batch,
instance_len,
vertex_len,
vertex_first,
expanded_prim_type,
expanded_prim_len,
ResourceID(res_id)};
}
}
void generate_commands(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
SubPassVector &sub_passes);
void bind(RecordingState &state);
private:
static void finalize_commands(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
SubPassVector &sub_passes,
uint &resource_id_count,
ResourceIdBuf &resource_id_buf);
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Multi Draw Commands
*
* For efficient rendering of large scene we strive to minimize the number of draw call and state
* changes. To this end, we group many rendering commands and sort them per render state using
* `DrawGroup` as a container. This is done automatically for any successive commands with the
* same state.
*
* A `DrawGroup` is the combination of a `gpu::Batch` (VBO state) and a `command::DrawMulti`
* (Pipeline State).
*
* Inside each `DrawGroup` all instances of a same `gpu::Batch` is merged into a single indirect
* command.
*
* To support this arbitrary reordering, we only need to know the offset of all the commands for a
* specific `DrawGroup`. This is done on CPU by doing a simple prefix sum. The result is pushed to
* GPU and used on CPU to issue the right command indirect.
*
* Each draw command is stored in an unsorted array of `DrawPrototype` and sent directly to the
* GPU.
*
* A command generation compute shader then go over each `DrawPrototype`. For each it adds it (or
* not depending on visibility) to the correct draw command using the offset of the `DrawGroup`
* computed on CPU. After that, it also outputs one resource ID for each instance inside a
* `DrawPrototype`.
*
* \{ */
class DrawMultiBuf {
friend Manager;
friend DrawMulti;
private:
using DrawGroupBuf = StorageArrayBuffer<DrawGroup, 16>;
using DrawPrototypeBuf = StorageArrayBuffer<DrawPrototype, 16>;
using DrawCommandBuf = StorageArrayBuffer<DrawCommand, 16, true>;
using ResourceIdBuf = StorageArrayBuffer<uint, 128, true>;
using DrawGroupKey = std::pair<uint, gpu::Batch *>;
using DrawGroupMap = Map<DrawGroupKey, uint>;
/** Maps a DrawMulti command and a gpu batch to their unique DrawGroup command. */
DrawGroupMap group_ids_;
/** DrawGroup Command heap. Uploaded to GPU for sorting. */
DrawGroupBuf group_buf_ = {"DrawGroupBuf"};
/** Command Prototypes. Unsorted */
DrawPrototypeBuf prototype_buf_ = {"DrawPrototypeBuf"};
/** Command list generated by the sorting / compaction steps. Lives on GPU. */
DrawCommandBuf command_buf_ = {"DrawCommandBuf"};
/** Array of resource id. One per instance. Lives on GPU. */
ResourceIdBuf resource_id_buf_ = {"ResourceIdBuf"};
/** Give unique ID to each header so we can use that as hash key. */
uint header_id_counter_ = 0;
/** Number of groups inside group_buf_. */
uint group_count_ = 0;
/** Number of prototype command inside prototype_buf_. */
uint prototype_count_ = 0;
/** Used items in the resource_id_buf_. Not it's allocated length. */
uint resource_id_count_ = 0;
public:
void clear()
{
group_buf_.trim_to_next_power_of_2(group_count_);
/* Two commands per group (inverted and non-inverted scale). */
command_buf_.trim_to_next_power_of_2(group_count_ * 2);
prototype_buf_.trim_to_next_power_of_2(prototype_count_);
resource_id_buf_.trim_to_next_power_of_2(resource_id_count_);
header_id_counter_ = 0;
group_count_ = 0;
prototype_count_ = 0;
group_ids_.clear();
}
void append_draw(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
gpu::Batch *batch,
uint instance_len,
uint vertex_len,
uint vertex_first,
ResourceIDRange id_range,
uint custom_id,
GPUPrimType expanded_prim_type,
uint16_t expanded_prim_len)
{
BLI_assert(batch != nullptr);
/* Custom draw-calls cannot be batched and will produce one group per draw. */
const bool custom_group = ((vertex_first != 0 && vertex_first != -1) || vertex_len != -1);
BLI_assert(vertex_len != 0);
vertex_len = vertex_len == -1 ? 0 : vertex_len;
instance_len = instance_len != -1 ? instance_len : 1;
/* If there was some state changes since previous call, we have to create another command. */
if (headers.is_empty() || headers.last().type != Type::DrawMulti) {
uint index = commands.append_and_get_index({});
headers.append({Type::DrawMulti, index});
commands[index].draw_multi = {batch, this, uint(-1), header_id_counter_++};
}
DrawMulti &cmd = commands.last().draw_multi;
uint &group_id = group_ids_.lookup_or_add(DrawGroupKey(cmd.uuid, batch), uint(-1));
bool inverted = id_range.has_inverted_handedness();
for (auto res_id : id_range.id_range()) {
DrawPrototype &draw = prototype_buf_.get_or_resize(prototype_count_++);
draw.res_id = uint32_t(res_id);
draw.custom_id = custom_id;
draw.instance_len = instance_len;
draw.group_id = group_id;
if (group_id == uint(-1) || custom_group) {
uint new_group_id = group_count_++;
draw.group_id = new_group_id;
DrawGroup &group = group_buf_.get_or_resize(new_group_id);
group.next = cmd.group_first;
group.len = instance_len;
group.front_facing_len = inverted ? 0 : instance_len;
group.front_facing_counter = 0;
group.back_facing_counter = 0;
group.desc.vertex_len = vertex_len;
group.desc.vertex_first = vertex_first;
group.desc.gpu_batch = batch;
group.desc.expand_prim_type = expanded_prim_type;
group.desc.expand_prim_len = expanded_prim_len;
BLI_assert_msg(expanded_prim_len < (1 << 3),
"Not enough bits to store primitive expansion");
/* Custom group are not to be registered in the group_ids_. */
if (!custom_group) {
group_id = new_group_id;
}
/* For serialization only. Reset before use on GPU. */
(inverted ? group.back_facing_counter : group.front_facing_counter)++;
/* Append to list. */
cmd.group_first = new_group_id;
}
else {
DrawGroup &group = group_buf_[group_id];
group.len += instance_len;
group.front_facing_len += inverted ? 0 : instance_len;
/* For serialization only. Reset before use on GPU. */
(inverted ? group.back_facing_counter : group.front_facing_counter)++;
/* NOTE: We assume that primitive expansion is coupled to the shader itself. Meaning we
* rely on shader bind to isolate the expanded draws into their own group (as there could
* be regular draws and extended draws using the same batch mixed inside the same pass).
* This will cause issues if this assumption is broken. Also it is very hard to detect this
* case for error checking. At least we can check that expansion settings don't change
* inside a group. */
BLI_assert(group.desc.expand_prim_type == expanded_prim_type);
BLI_assert(group.desc.expand_prim_len == expanded_prim_len);
}
}
}
void generate_commands(Vector<Header, 0> &headers,
Vector<Undetermined, 0> &commands,
VisibilityBuf &visibility_buf,
int visibility_word_per_draw,
int view_len,
bool use_custom_ids);
void bind(RecordingState &state);
};
/** \} */
}; // namespace draw::command
} // namespace blender

View File

@@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
namespace blender::gpu {
class Batch;
}
namespace blender::draw::command {
#endif
/* -------------------------------------------------------------------- */
/** \name Multi Draw
* \{ */
/**
* A DrawGroup allow to split the command stream into batch-able chunks of commands with
* the same render state.
*/
struct [[host_shared]] DrawGroup {
/** Index of next #DrawGroup from the same header. */
uint next;
/**
* IMPORTANT: All the following 3 members do not take multi-view into account.
* They only count the number of input instances. The command generation shader must multiply
* them by view_len to get the correct indices for resource ids.
*/
/** Index of the first instances after sorting. */
uint start;
/** Total number of instances (including inverted facing). Needed to issue the draw call. */
uint len;
/** Number of non inverted scaling instances in this Group. */
uint front_facing_len;
/** #gpu::Batch values (or subrange of) copied to #DrawCommand after sorting. */
int vertex_len;
int vertex_first;
/* Set to -1 if not an indexed draw. */
int base_index;
/** Atomic counters used during command sorting. GPU only. Reset on CPU. */
/* Counts visible and invisible instances. Create draw-calls when it reaches `DrawGroup::len`. */
uint total_counter;
/* Counts only visible instance (counting multi-view). Used to issue the draw-calls. */
uint front_facing_counter;
uint back_facing_counter;
/* CPU specific region of the struct. Should be kept constant after recording.
* Can be used by GPU but needs to be initialized by GPU before usage. */
#ifndef GPU_SHADER
struct {
/* Specific range of vertex to draw from the #gpu::Batch. */
uint32_t vertex_first;
/* Ugly packing to support expanded draws without inflating the struct.
* Makes vertex range restricted to smaller range for expanded draw. */
uint32_t expand_prim_type : 4;
uint32_t expand_prim_len : 3;
uint32_t vertex_len : 25;
/** Needed to create the correct draw call. */
gpu::Batch *gpu_batch;
uint64_t _cpu_pad0;
} desc;
#else
uint _cpu_reserved_1;
uint _cpu_reserved_2;
uint _cpu_reserved_3;
uint _cpu_reserved_4;
uint _cpu_reserved_5;
uint _cpu_reserved_6;
#endif
};
BLI_STATIC_ASSERT_ALIGN(DrawGroup, 16)
/**
* Representation of a future draw call inside a DrawGroup. This #DrawPrototype is then
* converted into #DrawCommand on GPU after visibility and compaction. Multiple
* #DrawPrototype might get merged into the same final #DrawCommand.
*/
struct [[host_shared]] DrawPrototype {
/* Reference to parent DrawGroup to get the gpu::Batch vertex / instance count. */
uint group_id;
/* Resource handle associated with this call. Also reference visibility. */
uint res_id;
/* Custom extra value to be used by the engines. */
uint custom_id;
/* Number of instances. */
uint instance_len;
};
/** \} */
#ifndef GPU_SHADER
}; // namespace blender::draw::command
#endif

View File

@@ -0,0 +1,93 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "draw_common_c.hh"
#include "draw_manager.hh"
#include "draw_pass.hh"
namespace blender::draw {
/** Hair. */
void hair_init();
/**
* \note Only valid after #DRW_curves_update().
*/
gpu::VertBuf *hair_pos_buffer_get(Scene *scene,
Object *object,
ParticleSystem *psys,
ModifierData *md);
gpu::Batch *hair_sub_pass_setup(PassMain::Sub &sub_ps,
const Scene *scene,
const ObjectRef &ob_ref,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material = nullptr);
gpu::Batch *hair_sub_pass_setup(PassSimple::Sub &sub_ps,
const Scene *scene,
const ObjectRef &ob_ref,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material = nullptr);
/** Curves. */
/**
* \note Content of the vertex buf is only valid after #DRW_curves_update().
*/
gpu::VertBuf *curves_pos_buffer_get(Object *object);
gpu::Batch *curves_sub_pass_setup(PassMain::Sub &ps,
const Scene *scene,
Object *ob,
const char *&r_error,
GPUMaterial *gpu_material = nullptr);
gpu::Batch *curves_sub_pass_setup(PassSimple::Sub &ps,
const Scene *scene,
Object *ob,
const char *&r_error,
GPUMaterial *gpu_material = nullptr);
/* Point cloud. */
gpu::Batch *pointcloud_sub_pass_setup(PassMain::Sub &sub_ps,
Object *object,
GPUMaterial *gpu_material = nullptr);
gpu::Batch *pointcloud_sub_pass_setup(PassSimple::Sub &sub_ps,
Object *object,
GPUMaterial *gpu_material = nullptr);
/** Volume. */
/**
* Add attribute bindings of volume grids to an existing pass.
* No draw call is added so the caller can decide how to use the data.
* \return nullptr if there is nothing to draw.
*/
PassMain::Sub *volume_sub_pass(PassMain::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material);
/**
* Add attribute bindings of volume grids to an existing pass.
* No draw call is added so the caller can decide how to use the data.
* \return nullptr if there is nothing to draw.
*/
PassSimple::Sub *volume_sub_pass(PassSimple::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material);
} // namespace blender::draw

View File

@@ -0,0 +1,74 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
namespace blender {
struct FluidModifierData;
struct GPUMaterial;
namespace gpu {
class Texture;
class UniformBuf;
class VertBuf;
} // namespace gpu
struct ModifierData;
struct Object;
struct ParticleSystem;
struct RegionView3D;
struct ViewLayer;
struct Scene;
struct DRWData;
namespace draw {
class Manager;
struct CurvesModule;
struct PointCloudModule;
struct VolumeModule;
class ObjectRef;
} // namespace draw
/* draw_curves.cc */
namespace draw {
/* If drw_data is nullptr, DST global is accessed to get it. */
void DRW_curves_init(DRWData *drw_data = nullptr);
void DRW_curves_begin_sync(DRWData *drw_data);
void DRW_curves_module_free(draw::CurvesModule *module);
void DRW_curves_update(draw::Manager &manager);
/* draw_pointcloud.cc */
/* If drw_data is nullptr, DST global is accessed to get it. */
void DRW_pointcloud_init(DRWData *drw_data = nullptr);
void DRW_pointcloud_module_free(draw::PointCloudModule *module);
/* draw_volume.cc */
/* If drw_data is nullptr, DST global is accessed to get it. */
void DRW_volume_init(DRWData *drw_data = nullptr);
void DRW_volume_module_free(draw::VolumeModule *module);
} // namespace draw
/* `draw_fluid.cc` */
/* Fluid simulation. */
void DRW_smoke_ensure(FluidModifierData *fmd, int highres);
void DRW_smoke_ensure_coba_field(FluidModifierData *fmd);
void DRW_smoke_ensure_velocity(FluidModifierData *fmd);
void DRW_fluid_ensure_flags(FluidModifierData *fmd);
void DRW_fluid_ensure_range_field(FluidModifierData *fmd);
void DRW_smoke_free(FluidModifierData *fmd);
void DRW_smoke_begin_sync(DRWData *drw_data);
void DRW_smoke_exit(DRWData *drw_data);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
/* Private functions / structs of the draw manager */
#pragma once
#include "DRW_engine.hh"
#include "DRW_render.hh"
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_vector_set.hh"
#include "GPU_batch.hh"
#include "GPU_context.hh"
#include "GPU_framebuffer.hh"
#include "GPU_viewport.hh"
struct DRWDebugModule;
struct DRWUniformChunk;
namespace blender {
struct DRWViewData;
struct DRWTextStore;
struct DupliObject;
struct Object;
struct Mesh;
namespace draw {
struct CurvesModule;
struct VolumeModule;
struct PointCloudModule;
struct DRW_MeshCDMask;
class CurveRefinePass;
class View;
} // namespace draw
struct GPUMaterial;
/* -------------------------------------------------------------------- */
/** \name Memory Pools
* \{ */
/** Contains memory pools information. */
struct DRWData {
/** Instance data. */
DRWInstanceDataList *idatalist;
/** List of smoke textures to free after drawing. */
ListBaseT<LinkData> smoke_textures;
/** Per stereo view data. Contains engine data and default frame-buffers. */
DRWViewData *view_data[2];
/** Module storage. */
draw::CurvesModule *curves_module;
draw::VolumeModule *volume_module;
draw::PointCloudModule *pointcloud_module;
/** Default view that feeds every engine. */
draw::View *default_view;
/* Ensure modules are created. */
void modules_init();
/* Callbacks before each sync cycle. */
void modules_begin_sync();
/* Callbacks after one draw to clear transient data. */
void modules_exit();
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Functions
* \{ */
/* Get thread local draw context. */
inline DRWContext &drw_get()
{
return DRWContext::get_active();
}
namespace draw {
void drw_batch_cache_validate(Object *ob);
void drw_batch_cache_generate_requested(Object *ob, TaskGraph &task_graph);
/**
* \warning Only evaluated mesh data is handled by this delayed generation.
*/
void drw_batch_cache_generate_requested_delayed(Object *ob);
void drw_batch_cache_generate_requested_evaluated_mesh_or_curve(Object *ob, TaskGraph &task_graph);
void DRW_mesh_get_attributes(const Object &object,
const Mesh &mesh,
Span<const GPUMaterial *> materials,
VectorSet<std::string> *r_attrs,
DRW_MeshCDMask *r_cd_needed);
} // namespace draw
/** \} */
} // namespace blender

View File

@@ -0,0 +1,568 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Contains procedural GPU hair drawing methods.
*/
#include "BLT_translation.hh"
#include "DNA_curves_types.h"
#include "BLI_math_base.h"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "GPU_material.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "draw_cache_impl.hh"
#include "draw_common.hh"
#include "draw_context_private.hh"
#include "draw_curves_defines.hh"
#include "draw_curves_private.hh"
#include "draw_hair_private.hh"
#include "draw_shader.hh"
namespace blender::draw {
using CurvesInfosBuf = UniformBuffer<CurvesInfos>;
CurvesInfosBuf &CurvesUniformBufPool::alloc()
{
CurvesInfosBuf *ptr;
if (used >= ubos.size()) {
ubos.append(std::make_unique<CurvesInfosBuf>());
ptr = ubos.last().get();
}
else {
ptr = ubos[used++].get();
}
memset(ptr->data(), 0, sizeof(CurvesInfos));
return *ptr;
}
gpu::VertBuf *CurvesModule::drw_curves_ensure_dummy_vbo()
{
GPUVertFormat format = {0};
uint dummy_id = GPU_vertformat_attr_add(&format, "dummy", gpu::VertAttrType::SFLOAT_32_32_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
GPU_vertbuf_data_alloc(*vbo, 1);
GPU_vertbuf_attr_fill(vbo, dummy_id, vert);
/* Create vbo immediately to bind to texture buffer. */
GPU_vertbuf_use(vbo);
return vbo;
}
void DRW_curves_init(DRWData *drw_data)
{
if (drw_data == nullptr) {
drw_data = drw_get().data;
}
if (drw_data->curves_module == nullptr) {
drw_data->curves_module = MEM_new<CurvesModule>("CurvesModule");
}
}
void DRW_curves_begin_sync(DRWData *drw_data)
{
drw_data->curves_module->init();
}
void DRW_curves_module_free(CurvesModule *curves_module)
{
MEM_delete(curves_module);
}
void CurvesModule::dispatch(const int curve_count, PassSimple::Sub &pass)
{
/* Note that the GPU_max_work_group_count can be INT_MAX.
* Promote to 64bit int to avoid overflow. */
const int64_t max_strands_per_call = int64_t(GPU_max_work_group_count(0)) *
CURVES_PER_THREADGROUP;
int strands_start = 0;
while (strands_start < curve_count) {
int batch_strands_len = std::min(int64_t(curve_count - strands_start), max_strands_per_call);
pass.push_constant("curves_start", strands_start);
pass.push_constant("curves_count", batch_strands_len);
pass.dispatch(divide_ceil_u(batch_strands_len, CURVES_PER_THREADGROUP));
strands_start += batch_strands_len;
}
}
gpu::VertBufPtr CurvesModule::evaluate_topology_indirection(const int curve_count,
const int point_count,
CurvesEvalCache &cache,
bool is_ribbon,
bool has_cyclic)
{
int element_count = is_ribbon ? (point_count + curve_count) : (point_count - curve_count);
if (has_cyclic) {
element_count += curve_count;
}
gpu::VertBufPtr indirection_buf = gpu::VertBuf::device_only<int>(element_count);
PassSimple::Sub &pass = refine.sub("Topology");
pass.shader_set(DRW_shader_curves_topology_get());
pass.bind_ssbo("evaluated_offsets_buf", cache.evaluated_points_by_curve_buf);
pass.bind_ssbo("curves_cyclic_buf", cache.curves_cyclic_buf);
pass.bind_ssbo("indirection_buf", indirection_buf);
pass.push_constant("is_ribbon_topology", is_ribbon);
pass.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, pass);
return indirection_buf;
}
void CurvesModule::evaluate_curve_attribute(const bool has_catmull,
const bool has_bezier,
const bool has_poly,
const bool has_nurbs,
const bool has_cyclic,
const int curve_count,
CurvesEvalCache &cache,
CurvesEvalShader shader_type,
gpu::VertBufPtr input_buf,
gpu::VertBufPtr &output_buf,
gpu::VertBuf *input2_buf /* = nullptr */,
float4x4 transform /* = float4x4::identity() */)
{
BLI_assert(input_buf != nullptr);
BLI_assert(output_buf != nullptr);
gpu::Shader *shader = DRW_shader_curves_refine_get(shader_type);
const char *pass_name = nullptr;
switch (shader_type) {
case CURVES_EVAL_POSITION:
pass_name = "Position";
break;
case CURVES_EVAL_FLOAT:
pass_name = "Float Attribute";
break;
case CURVES_EVAL_FLOAT2:
pass_name = "Float2 Attribute";
break;
case CURVES_EVAL_FLOAT3:
pass_name = "Float3 Attribute";
break;
case CURVES_EVAL_FLOAT4:
pass_name = "Float4 Attribute";
break;
case CURVES_EVAL_LENGTH_INTERCEPT:
pass_name = "Length-Intercept Attributes";
break;
}
PassSimple::Sub &pass = refine.sub(pass_name);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, cache.points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, cache.curves_type_buf);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, cache.curves_cyclic_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, cache.curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, cache.evaluated_points_by_curve_buf);
switch (shader_type) {
case CURVES_EVAL_POSITION:
pass.bind_ssbo(POINT_POSITIONS_SLOT, input_buf);
pass.bind_ssbo(POINT_RADII_SLOT, input2_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
/* Move ownership of the radius input vbo to the module. */
this->transient_buffers.append(gpu::VertBufPtr(input2_buf));
break;
case CURVES_EVAL_FLOAT:
case CURVES_EVAL_FLOAT2:
case CURVES_EVAL_FLOAT3:
case CURVES_EVAL_FLOAT4:
pass.bind_ssbo(POINT_ATTR_SLOT, input_buf);
pass.bind_ssbo(EVALUATED_ATTR_SLOT, output_buf);
break;
case CURVES_EVAL_LENGTH_INTERCEPT:
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, cache.evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, cache.curves_length_buf);
/* Synchronize positions reads. */
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
break;
}
if (has_catmull) {
PassSimple::Sub &sub = pass.sub("Catmull-Rom");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_CATMULL_ROM));
sub.shader_set(shader);
/* Dummy, not used for Catmull-Rom. */
sub.bind_ssbo("handles_positions_left_buf", this->dummy_vbo);
sub.bind_ssbo("handles_positions_right_buf", this->dummy_vbo);
sub.bind_ssbo("bezier_offsets_buf", this->dummy_vbo);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
if (has_bezier) {
PassSimple::Sub &sub = pass.sub("Bezier");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_BEZIER));
sub.shader_set(shader);
sub.bind_ssbo("handles_positions_left_buf", cache.handles_positions_left_buf);
sub.bind_ssbo("handles_positions_right_buf", cache.handles_positions_right_buf);
sub.bind_ssbo("bezier_offsets_buf", cache.bezier_offsets_buf);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
if (has_nurbs) {
PassSimple::Sub &sub = pass.sub("Nurbs");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_NURBS));
sub.shader_set(shader);
sub.bind_ssbo("curves_resolution_buf", cache.curves_order_buf);
sub.bind_ssbo("handles_positions_left_buf", cache.basis_cache_buf);
sub.bind_ssbo("handles_positions_right_buf",
cache.control_weights_buf.get() ? cache.control_weights_buf :
cache.basis_cache_buf);
sub.bind_ssbo("bezier_offsets_buf", cache.basis_cache_offset_buf);
sub.push_constant("use_point_weight", cache.control_weights_buf.get() != nullptr);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
if (has_poly) {
PassSimple::Sub &sub = pass.sub("Poly");
sub.specialize_constant(shader, "evaluated_type", int(CURVE_TYPE_POLY));
sub.shader_set(shader);
/* Dummy, not used for Poly. */
sub.bind_ssbo("curves_resolution_buf", this->dummy_vbo);
sub.bind_ssbo("handles_positions_left_buf", this->dummy_vbo);
sub.bind_ssbo("handles_positions_right_buf", this->dummy_vbo);
sub.bind_ssbo("bezier_offsets_buf", this->dummy_vbo);
/* Bake object transform for legacy hair particle. */
sub.push_constant("transform", transform);
sub.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, sub);
}
/* Move ownership of the input vbo to the module. */
this->transient_buffers.append(std::move(input_buf));
}
void CurvesModule::evaluate_curve_length_intercept(const bool has_cyclic,
const int curve_count,
CurvesEvalCache &cache)
{
gpu::Shader *shader = DRW_shader_curves_refine_get(CURVES_EVAL_LENGTH_INTERCEPT);
PassSimple::Sub &pass = refine.sub("Length-Intercept Attributes");
pass.shader_set(shader);
pass.bind_ssbo(POINTS_BY_CURVES_SLOT, cache.points_by_curve_buf);
pass.bind_ssbo(CURVE_TYPE_SLOT, cache.curves_type_buf);
pass.bind_ssbo(CURVE_CYCLIC_SLOT, cache.curves_cyclic_buf);
pass.bind_ssbo(CURVE_RESOLUTION_SLOT, cache.curves_resolution_buf);
pass.bind_ssbo(EVALUATED_POINT_SLOT, cache.evaluated_points_by_curve_buf);
pass.bind_ssbo(EVALUATED_POS_RAD_SLOT, cache.evaluated_pos_rad_buf);
pass.bind_ssbo(EVALUATED_TIME_SLOT, cache.evaluated_time_buf);
pass.bind_ssbo(CURVES_LENGTH_SLOT, cache.curves_length_buf);
pass.barrier(GPU_BARRIER_SHADER_STORAGE);
/* Bake object transform for legacy hair particle. */
pass.push_constant("use_cyclic", has_cyclic);
dispatch(curve_count, pass);
}
static int attribute_index_in_material(const GPUMaterial *gpu_material,
const StringRef name,
bool is_curve_length = false,
bool is_curve_intercept = false)
{
if (!gpu_material) {
return -1;
}
int index = 0;
ListBaseT<GPUMaterialAttribute> gpu_attrs = GPU_material_attributes(gpu_material);
for (GPUMaterialAttribute &gpu_attr : gpu_attrs) {
if (is_curve_length) {
if (gpu_attr.is_hair_length) {
return index;
}
}
else if (is_curve_intercept) {
if (gpu_attr.is_hair_intercept) {
return index;
}
}
else if (!gpu_attr.is_hair_intercept && !gpu_attr.is_hair_length && gpu_attr.name == name) {
return index;
}
index++;
}
return -1;
}
void DRW_curves_update(draw::Manager &manager)
{
DRW_submission_start();
/* TODO(fclem): Remove Global access. */
CurvesModule &module = *drw_get().data->curves_module;
/* NOTE: This also update legacy hairs too as they populate the same pass. */
manager.submit(module.refine);
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
module.transient_buffers.clear();
/* Make sure calling this function again will not subdivide the same data. */
module.refine.init();
DRW_submission_end();
}
/* New Draw Manager. */
gpu::VertBuf *curves_pos_buffer_get(Object *object)
{
CurvesModule &module = *drw_get().data->curves_module;
Curves &curves = DRW_object_get_data_for_drawing<Curves>(*object);
CurvesEvalCache &cache = curves_get_eval_cache(curves);
cache.ensure_positions(module, curves.geometry.wrap());
return cache.evaluated_pos_rad_buf.get();
}
static std::optional<StringRef> get_first_uv_name(const bke::AttributeAccessor &attributes)
{
std::optional<StringRef> name;
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.data_type == bke::AttrType::Float2) {
name = iter.name;
iter.stop();
}
});
return name;
}
/* Return true if attribute exists in shader. */
static bool set_attribute_type(const GPUMaterial *gpu_material,
const StringRef name,
CurvesInfosBuf &curves_infos,
const bool is_point_domain)
{
/* Some attributes may not be used in the shader anymore and were not garbage collected yet, so
* we need to find the right index for this attribute as uniforms defining the scope of the
* attributes are based on attribute loading order, which is itself based on the material's
* attributes. */
const int index = attribute_index_in_material(gpu_material, name);
if (index == -1) {
return false;
}
curves_infos.is_point_attribute[index][0] = is_point_domain;
return true;
}
template<typename PassT>
void curves_bind_resources_implementation(PassT &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
const std::optional<StringRef> uv_name)
{
/* Ensure we have no unbound resources.
* Required for Vulkan.
* Fixes issues with certain GL drivers not drawing anything. */
sub_ps.bind_texture("u", module.dummy_vbo);
sub_ps.bind_texture("au", module.dummy_vbo);
sub_ps.bind_texture("a", module.dummy_vbo);
sub_ps.bind_texture("c", module.dummy_vbo);
sub_ps.bind_texture("ac", module.dummy_vbo);
sub_ps.bind_texture("l", module.dummy_vbo);
sub_ps.bind_texture("i", module.dummy_vbo);
if (gpu_material) {
ListBaseT<GPUMaterialAttribute> attr_list = GPU_material_attributes(gpu_material);
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
for (const GPUMaterialAttribute *attr : attrs) {
sub_ps.bind_texture(attr->input_name, module.dummy_vbo);
}
}
CurvesInfosBuf &curves_infos = module.ubo_pool.alloc();
{
/* TODO(fclem): Compute only if needed. */
const int index = attribute_index_in_material(gpu_material, "", true, false);
if (index != -1) {
sub_ps.bind_texture("l", cache.curves_length_buf);
curves_infos.is_point_attribute[index][0] = false;
}
}
{
/* TODO(fclem): Compute only if needed. */
const int index = attribute_index_in_material(gpu_material, "", false, true);
if (index != -1) {
sub_ps.bind_texture("i", cache.evaluated_time_buf);
curves_infos.is_point_attribute[index][0] = true;
}
}
const VectorSet<std::string> &attrs = cache.attr_used;
for (const int i : attrs.index_range()) {
const StringRef name = attrs[i];
char sampler_name[32];
drw_curves_get_attribute_sampler_name(name, sampler_name);
if (cache.attributes_point_domain[i]) {
if (!cache.evaluated_attributes_buf[i]) {
continue;
}
if (set_attribute_type(gpu_material, name, curves_infos, true)) {
sub_ps.bind_texture(sampler_name, cache.evaluated_attributes_buf[i]);
}
if (name == uv_name) {
if (set_attribute_type(gpu_material, "", curves_infos, true)) {
sub_ps.bind_texture("a", cache.evaluated_attributes_buf[i]);
}
}
}
else {
if (!cache.curve_attributes_buf[i]) {
continue;
}
if (set_attribute_type(gpu_material, name, curves_infos, false)) {
sub_ps.bind_texture(sampler_name, cache.curve_attributes_buf[i]);
}
if (name == uv_name) {
if (set_attribute_type(gpu_material, "", curves_infos, false)) {
sub_ps.bind_texture("a", cache.curve_attributes_buf[i]);
}
}
}
}
curves_infos.half_cylinder_face_count = face_per_segment;
curves_infos.vertex_per_segment = face_per_segment < 2 ? (face_per_segment + 1) :
((face_per_segment + 1) * 2 + 1);
curves_infos.push_update();
sub_ps.bind_ubo("drw_curves", curves_infos);
sub_ps.bind_texture("curves_pos_rad_buf", cache.evaluated_pos_rad_buf);
sub_ps.bind_texture("curves_indirection_buf", indirection_buf);
}
void curves_bind_resources(PassMain::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
const std::optional<StringRef> active_uv_name)
{
curves_bind_resources_implementation(
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name);
}
void curves_bind_resources(PassSimple::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
const std::optional<StringRef> active_uv_name)
{
curves_bind_resources_implementation(
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv_name);
}
template<typename PassT>
gpu::Batch *curves_sub_pass_setup_implementation(PassT &sub_ps,
const Scene *scene,
Object *ob,
const char *&r_error,
GPUMaterial *gpu_material = nullptr)
{
BLI_assert(ob->type == OB_CURVES);
Curves &curves_id = DRW_object_get_data_for_drawing<Curves>(*ob);
const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
const int face_per_segment = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 0 :
(scene->r.hair_type == SCE_HAIR_SHAPE_CYLINDER) ? 3 :
1;
CurvesEvalCache &curves_cache = curves_get_eval_cache(curves_id);
if (curves.curves_num() == 0) {
/* Nothing to draw. Just return an empty drawcall that will be skipped. */
bool unused_error = false;
return curves_cache.batch_get(0, 0, face_per_segment, false, unused_error);
}
CurvesModule &module = *drw_get().data->curves_module;
curves_cache.ensure_positions(module, curves);
curves_cache.ensure_attributes(module, curves, gpu_material);
gpu::VertBufPtr &indirection_buf = curves_cache.indirection_buf_get(
module, curves, face_per_segment);
const std::optional<StringRef> uv_name = get_first_uv_name(
curves_id.geometry.wrap().attributes());
curves_bind_resources(
sub_ps, module, curves_cache, face_per_segment, gpu_material, indirection_buf, uv_name);
bool error = false;
gpu::Batch *batch = curves_cache.batch_get(curves.evaluated_points_num(),
curves.curves_num(),
face_per_segment,
curves.has_cyclic_curve(),
error);
if (error) {
r_error = RPT_(
"Error: Curves object contains too many points. "
"Reduce curve resolution or curve count to fix this issue.\n");
}
return batch;
}
gpu::Batch *curves_sub_pass_setup(PassMain::Sub &ps,
const Scene *scene,
Object *ob,
const char *&r_error,
GPUMaterial *gpu_material)
{
return curves_sub_pass_setup_implementation(ps, scene, ob, r_error, gpu_material);
}
gpu::Batch *curves_sub_pass_setup(PassSimple::Sub &ps,
const Scene *scene,
Object *ob,
const char *&r_error,
GPUMaterial *gpu_material)
{
return curves_sub_pass_setup_implementation(ps, scene, ob, r_error, gpu_material);
}
} // namespace blender::draw

View File

@@ -0,0 +1,44 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* List of defines that are shared with the GPUShaderCreateInfos. We do this to avoid
* dragging larger headers into the createInfo pipeline which would cause problems.
*/
#pragma once
/** Curves evaluation. */
#define CURVES_PER_THREADGROUP 64
#define POINTS_BY_CURVES_SLOT 0
#define CURVE_TYPE_SLOT 1
#define CURVE_RESOLUTION_SLOT 2
#define EVALUATED_POINT_SLOT 3
#define CURVE_CYCLIC_SLOT 4
#define HANDLES_POS_LEFT_SLOT 5
#define HANDLES_POS_RIGHT_SLOT 6
#define BEZIER_OFFSETS_SLOT 7
/* Nurbs (alias of other buffers). */
#define CURVES_ORDER_SLOT CURVE_RESOLUTION_SLOT
#define BASIS_CACHE_SLOT HANDLES_POS_LEFT_SLOT
#define CONTROL_WEIGHTS_SLOT HANDLES_POS_RIGHT_SLOT
#define BASIS_CACHE_OFFSET_SLOT BEZIER_OFFSETS_SLOT
/* Position evaluation. */
#define POINT_POSITIONS_SLOT 8
#define POINT_RADII_SLOT 9
#define EVALUATED_POS_RAD_SLOT 10
/* Attribute evaluation. */
#define POINT_ATTR_SLOT 8
#define EVALUATED_ATTR_SLOT 9
/* Intercept evaluation. */
#define EVALUATED_TIME_SLOT 8
#define CURVES_LENGTH_SLOT 9

View File

@@ -0,0 +1,279 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include <array>
#include <string>
#include "GPU_shader.hh"
#include "GPU_vertex_buffer.hh"
#include "BLI_vector_set.hh"
#include "draw_pass.hh"
namespace blender {
struct Curves;
struct Object;
struct ParticleSystem;
struct PTCacheEdit;
struct ModifierData;
struct ParticleCacheKey;
namespace bke {
class CurvesGeometry;
} // namespace bke
namespace gpu {
class Batch;
class VertBuf;
} // namespace gpu
struct GPUMaterial;
namespace draw {
struct CurvesModule;
#define MAX_FACE_PER_SEGMENT 5
#define MAX_HAIR_SUBDIV 4 /* see hair_subdiv rna */
enum CurvesEvalShader {
CURVES_EVAL_POSITION = 0,
CURVES_EVAL_FLOAT = 1,
CURVES_EVAL_FLOAT2 = 2,
CURVES_EVAL_FLOAT3 = 3,
CURVES_EVAL_FLOAT4 = 4,
CURVES_EVAL_LENGTH_INTERCEPT = 5,
};
/* Legacy Hair Particle. */
struct ParticleSpans {
Span<ParticleCacheKey *> parent;
Span<ParticleCacheKey *> children;
void foreach_strand(FunctionRef<void(Span<ParticleCacheKey>)> callback);
};
struct ParticleDrawSource {
public:
Object *object = nullptr;
ParticleSystem *psys = nullptr;
ModifierData *md = nullptr;
PTCacheEdit *edit = nullptr;
private:
Vector<int> &points_by_curve_storage_;
Vector<int> &evaluated_points_by_curve_storage_;
int additional_subdivision_;
public:
ParticleDrawSource(Vector<int> &points_by_curve_storage,
Vector<int> &evaluated_points_by_curve_storage,
int additional_subdivision)
: points_by_curve_storage_(points_by_curve_storage),
evaluated_points_by_curve_storage_(evaluated_points_by_curve_storage),
additional_subdivision_(additional_subdivision)
{
}
int curves_num()
{
if (points_by_curve_storage_.is_empty()) {
points_by_curve();
}
return points_by_curve_storage_.size() - 1;
}
int points_num()
{
if (points_by_curve_storage_.is_empty()) {
points_by_curve();
}
return points_by_curve_storage_.last();
}
int evaluated_points_num()
{
if (additional_subdivision_ == 0) {
return points_num();
}
evaluated_points_by_curve();
return evaluated_points_by_curve_storage_.last();
}
int resolution()
{
return 1 << additional_subdivision_;
}
OffsetIndices<int> points_by_curve();
OffsetIndices<int> evaluated_points_by_curve();
ParticleSpans particles_get();
};
#define CURVES_EVAL_SHADER_NUM 5
/* Curves procedural display: Evaluation is done on the GPU. */
struct CurvesEvalCache {
/* --- Required attributes. --- */
/** Position and radius per evaluated point. Always evaluated. */
gpu::VertBufPtr evaluated_pos_rad_buf;
/** Intercept time per evaluated point. */
/* TODO(fclem): Move it to generic point domain attributes. */
gpu::VertBufPtr evaluated_time_buf;
/** Intercept time per curve. */
/* TODO(fclem): Move it to generic curve domain attributes. */
gpu::VertBufPtr curves_length_buf;
/* --- Indirection buffers. --- */
/* Map primitive to point ID and curve ID. Contains restart indices for line and triangle strip
* primitive. */
gpu::VertBufPtr indirection_ribbon_buf;
/* Map primitive to point ID and curve ID. Compacted for cylinder primitive. */
gpu::VertBufPtr indirection_cylinder_buf;
/* --- Buffers common to all curve types. --- */
/** Buffer containing `CurveGeometry::points_by_curve()`. */
gpu::VertBufPtr points_by_curve_buf;
/** Buffer containing `CurveGeometry::evaluated_points_by_curve()`. */
gpu::VertBufPtr evaluated_points_by_curve_buf;
/** Buffer containing `CurveGeometry::curve_types()`. */
gpu::VertBufPtr curves_type_buf;
/** Buffer containing `CurveGeometry::resolution()`. */
gpu::VertBufPtr curves_resolution_buf;
/** Buffer containing `CurveGeometry::cyclic_offsets()` or dummy data if not needed. */
gpu::VertBufPtr curves_cyclic_buf;
/* --- Buffers only needed if geometry has Bezier curves. Dummy sized otherwise. --- */
/** Buffer containing `CurveGeometry::handle_positions_left()`. */
gpu::VertBufPtr handles_positions_left_buf;
/** Buffer containing `CurveGeometry::handle_positions_right()`. */
gpu::VertBufPtr handles_positions_right_buf;
/** Buffer containing `EvaluatedOffsets::all_bezier_offsets`. */
gpu::VertBufPtr bezier_offsets_buf;
/* --- Buffers only needed if geometry has Nurbs curves. Dummy sized otherwise. --- */
/** Buffer containing `CurveGeometry::nurbs_orders()`. */
gpu::VertBufPtr curves_order_buf;
/** Buffer containing `CurveGeometry::nurbs_weights()`. */
gpu::VertBufPtr control_weights_buf;
/** Buffer containing all `nurbs::BasisCache` concatenated. */
gpu::VertBufPtr basis_cache_buf;
/** Buffer containing offsets to the start of each `nurbs::BasisCache` for each curve. */
gpu::VertBufPtr basis_cache_offset_buf;
/* --- Generic Attributes. --- */
/** Attributes currently being drawn or about to be drawn. */
VectorSet<std::string> attr_used;
/**
* Attributes that were used at some point. This is used for garbage collection, to remove
* attributes that are not used in shaders anymore due to user edits.
*/
VectorSet<std::string> attr_used_over_time;
/**
* The last time in seconds that the `attr_used` and `attr_used_over_time` were exactly the same.
* If the delta between this time and the current scene time is greater than the timeout set in
* user preferences (`U.vbotimeout`) then garbage collection is performed.
*/
int last_attr_matching_time;
/* Attributes stored per curve. Nullptr if attribute is not from this domain. */
gpu::VertBufPtr curve_attributes_buf[GPU_MAX_ATTR];
/* Output of the evaluation stage. This is only used by attributes on point domain. */
gpu::VertBufPtr evaluated_attributes_buf[GPU_MAX_ATTR];
/* If attribute is point domain, use evaluated_attributes_buf. Otherwise curve_attributes_buf. */
std::array<bool, GPU_MAX_ATTR> attributes_point_domain;
/* --- Procedural Drawcalls. --- */
std::array<gpu::Batch *, MAX_FACE_PER_SEGMENT> batch;
void ensure_attribute(struct CurvesModule &module,
const bke::CurvesGeometry &curves,
StringRef name,
int index);
void ensure_attributes(struct CurvesModule &module,
const bke::CurvesGeometry &curves,
const GPUMaterial *gpu_material);
void ensure_common(const bke::CurvesGeometry &curves);
void ensure_bezier(const bke::CurvesGeometry &curves);
void ensure_nurbs(const bke::CurvesGeometry &curves);
void ensure_positions(CurvesModule &module, const bke::CurvesGeometry &curves);
gpu::VertBufPtr &indirection_buf_get(CurvesModule &module,
const bke::CurvesGeometry &curves,
int face_per_segment);
/* Sets r_over_limit to true if reaching hardware limit for the number of segments. */
gpu::Batch *batch_get(int evaluated_point_count,
int curve_count,
int face_per_segment,
bool use_cyclic,
bool &r_over_limit);
void discard_attributes();
void clear();
/* --- Legacy Hair Particle system. --- */
int resolution = 0;
void ensure_attribute(CurvesModule &module,
ParticleDrawSource &src,
const Mesh &mesh,
const StringRef name,
const int index);
void ensure_attributes(CurvesModule &module,
ParticleDrawSource &src,
const GPUMaterial *gpu_material);
void ensure_common(ParticleDrawSource &src);
void ensure_positions(CurvesModule &module, ParticleDrawSource &src);
gpu::VertBufPtr &indirection_buf_get(CurvesModule &module,
ParticleDrawSource &src,
int face_per_segment);
private:
/* In the case where there is cyclic curves, add one padding point per curve to ensure easy
* indexing in the drawing shader. */
int evaluated_point_count_with_cyclic(const bke::CurvesGeometry &curves);
};
CurvesEvalCache &curves_get_eval_cache(Curves &curves_id);
void drw_curves_get_attribute_sampler_name(StringRef layer_name, char r_sampler_name[32]);
void curves_bind_resources(draw::PassMain::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
std::optional<StringRef> active_uv_name);
void curves_bind_resources(draw::PassSimple::Sub &sub_ps,
CurvesModule &module,
CurvesEvalCache &cache,
const int face_per_segment,
GPUMaterial *gpu_material,
gpu::VertBufPtr &indirection_buf,
std::optional<StringRef> active_uv_name);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,297 @@
/* SPDX-FileCopyrightText: 2018 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Simple API to draw debug shapes in the viewport.
*/
#include "BKE_object.hh"
#include "BLI_math_bits.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "GPU_batch.hh"
#include "GPU_debug.hh"
#include "draw_context_private.hh"
#include "draw_debug.hh"
#include "draw_shader.hh"
#include "draw_shader_shared.hh"
namespace blender::draw {
/* -------------------------------------------------------------------- */
/** \name Init and state
* \{ */
void DebugDraw::reset()
{
for (int i = 0; i < 2; i++) {
vertex_len_.store(0);
if (cpu_draw_buf_.current() == nullptr) {
cpu_draw_buf_.current() = MEM_new<DebugDrawBuf>("DebugDrawBuf-CPU", "DebugDrawBuf-CPU");
gpu_draw_buf_.current() = MEM_new<DebugDrawBuf>("DebugDrawBuf-GPU", "DebugDrawBuf-GPU");
}
cpu_draw_buf_.current()->command.array().vertex_len = 0;
cpu_draw_buf_.current()->command.array().vertex_first = 0;
cpu_draw_buf_.current()->command.array().instance_len = 1;
cpu_draw_buf_.current()->command.array().instance_first = 0;
gpu_draw_buf_.current()->command.array().vertex_len = 0;
gpu_draw_buf_.current()->command.array().vertex_first = 0;
gpu_draw_buf_.current()->command.array().instance_len = 1;
gpu_draw_buf_.current()->command.array().instance_first = 0;
gpu_draw_buf_.current()->push_update();
cpu_draw_buf_.swap();
gpu_draw_buf_.swap();
}
gpu_draw_buf_used = false;
}
gpu::StorageBuf *DebugDraw::gpu_draw_buf_get()
{
#ifdef WITH_DRAW_DEBUG
gpu_draw_buf_used = true;
return *gpu_draw_buf_.current();
#else
return nullptr;
#endif
}
void DebugDraw::clear_gpu_data()
{
for (int i = 0; i < 2; i++) {
MEM_SAFE_DELETE(cpu_draw_buf_.current());
MEM_SAFE_DELETE(gpu_draw_buf_.current());
cpu_draw_buf_.swap();
gpu_draw_buf_.swap();
}
}
void drw_debug_clear()
{
DebugDraw::get().reset();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Draw functions
* \{ */
void drw_debug_line(const float3 v1, const float3 v2, const float4 color, const uint lifetime)
{
DebugDraw &dd = DebugDraw::get();
dd.draw_line(v1, v2, debug_color_pack(color), lifetime);
}
void drw_debug_polygon(Span<float3> face_verts, const float4 color, const uint lifetime)
{
BLI_assert(!face_verts.is_empty());
DebugDraw &dd = DebugDraw::get();
uint col = debug_color_pack(color);
float3 v0 = face_verts.last();
for (auto vert : face_verts) {
float3 v1 = vert;
dd.draw_line(v0, v1, col, lifetime);
v0 = v1;
}
}
void drw_debug_bbox(const BoundBox &bbox, const float4 color, const uint lifetime)
{
DebugDraw &dd = DebugDraw::get();
uint col = debug_color_pack(color);
dd.draw_line(bbox.vec[0], bbox.vec[1], col, lifetime);
dd.draw_line(bbox.vec[1], bbox.vec[2], col, lifetime);
dd.draw_line(bbox.vec[2], bbox.vec[3], col, lifetime);
dd.draw_line(bbox.vec[3], bbox.vec[0], col, lifetime);
dd.draw_line(bbox.vec[4], bbox.vec[5], col, lifetime);
dd.draw_line(bbox.vec[5], bbox.vec[6], col, lifetime);
dd.draw_line(bbox.vec[6], bbox.vec[7], col, lifetime);
dd.draw_line(bbox.vec[7], bbox.vec[4], col, lifetime);
dd.draw_line(bbox.vec[0], bbox.vec[4], col, lifetime);
dd.draw_line(bbox.vec[1], bbox.vec[5], col, lifetime);
dd.draw_line(bbox.vec[2], bbox.vec[6], col, lifetime);
dd.draw_line(bbox.vec[3], bbox.vec[7], col, lifetime);
}
static Vector<float3> precompute_sphere_points(int circle_resolution)
{
Vector<float3> result;
for (auto axis : IndexRange(3)) {
for (auto edge : IndexRange(circle_resolution)) {
for (auto vert : IndexRange(2)) {
const float angle = (2 * M_PI) * (edge + vert) / float(circle_resolution);
const float point[3] = {cosf(angle), sinf(angle), 0.0f};
result.append(float3(point[(0 + axis) % 3], point[(1 + axis) % 3], point[(2 + axis) % 3]));
}
}
}
return result;
}
void drw_debug_sphere(const float3 center, float radius, const float4 color, const uint lifetime)
{
/** Precomputed shapes verts. */
static Vector<float3> sphere_verts = precompute_sphere_points(16);
DebugDraw &dd = DebugDraw::get();
uint col = debug_color_pack(color);
for (auto i : IndexRange(sphere_verts.size() / 2)) {
float3 v0 = sphere_verts[i * 2] * radius + center;
float3 v1 = sphere_verts[i * 2 + 1] * radius + center;
dd.draw_line(v0, v1, col, lifetime);
}
}
void drw_debug_point(const float3 pos, float rad, const float4 col, const uint lifetime)
{
static Vector<float3> point_verts = precompute_sphere_points(4);
DebugDraw &dd = DebugDraw::get();
uint color = debug_color_pack(col);
for (auto i : IndexRange(point_verts.size() / 2)) {
float3 v0 = point_verts[i * 2] * rad + pos;
float3 v1 = point_verts[i * 2 + 1] * rad + pos;
dd.draw_line(v0, v1, color, lifetime);
}
}
void drw_debug_matrix(const float4x4 &m4, const uint lifetime)
{
float3 v0 = math::transform_point(m4, float3(0.0f, 0.0f, 0.0f));
float3 v1 = math::transform_point(m4, float3(1.0f, 0.0f, 0.0f));
float3 v2 = math::transform_point(m4, float3(0.0f, 1.0f, 0.0f));
float3 v3 = math::transform_point(m4, float3(0.0f, 0.0f, 1.0f));
DebugDraw &dd = DebugDraw::get();
dd.draw_line(v0, v1, debug_color_pack(float4(1.0f, 0.0f, 0.0f, 1.0f)), lifetime);
dd.draw_line(v0, v2, debug_color_pack(float4(0.0f, 1.0f, 0.0f, 1.0f)), lifetime);
dd.draw_line(v0, v3, debug_color_pack(float4(0.0f, 0.0f, 1.0f, 1.0f)), lifetime);
}
void drw_debug_matrix_as_bbox(const float4x4 &mat, const float4 color, const uint lifetime)
{
BoundBox bb;
std::array<float3, 8> corners = bounds::corners(Bounds<float3>(float3(-1), float3(1)));
for (auto i : IndexRange(8)) {
mul_project_m4_v3(mat.ptr(), corners[i]);
}
drw_debug_bbox(bb, color, lifetime);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internals
*
* \{ */
void DebugDraw::draw_line(float3 v1, float3 v2, uint color, const uint lifetime)
{
DebugDrawBuf &buf = *cpu_draw_buf_.current();
uint index = vertex_len_.fetch_add(2);
if (index + 2 < DRW_DEBUG_DRAW_VERT_MAX) {
buf.verts[index / 2] = debug_line_make(float_as_uint(v1.x),
float_as_uint(v1.y),
float_as_uint(v1.z),
float_as_uint(v2.x),
float_as_uint(v2.y),
float_as_uint(v2.z),
color,
lifetime);
buf.command.array().vertex_len += 2;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Display
* \{ */
void DebugDraw::display_lines(View &view)
{
const bool cpu_draw_buf_used = vertex_len_.load() != 0;
if (!cpu_draw_buf_used && !gpu_draw_buf_used) {
return;
}
command::StateSet::set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
gpu::Batch *batch = GPU_batch_procedural_lines_get();
gpu::Shader *shader = DRW_shader_debug_draw_display_get();
GPU_batch_set_shader(batch, shader);
GPU_shader_uniform_mat4(shader, "persmat", view.persmat().ptr());
GPU_shader_uniform_2f(shader, "size_viewport", viewport_size[2], viewport_size[3]);
if (gpu_draw_buf_used) {
GPU_debug_group_begin("GPU");
/* Reset buffer. */
gpu_draw_buf_.next()->command.array().vertex_len = 0;
gpu_draw_buf_.next()->push_update();
GPU_storagebuf_bind(*gpu_draw_buf_.current(), DRW_DEBUG_DRAW_SLOT);
GPU_storagebuf_bind(*gpu_draw_buf_.next(), DRW_DEBUG_DRAW_FEEDBACK_SLOT);
GPU_batch_draw_indirect(batch, *gpu_draw_buf_.current(), 0);
GPU_storagebuf_unbind(*gpu_draw_buf_.current());
GPU_storagebuf_unbind(*gpu_draw_buf_.next());
GPU_debug_group_end();
}
{
GPU_debug_group_begin("CPU");
/* We might have race condition here (a writer thread might still be outputting vertices).
* But that is ok. At worse, we will be missing some vertex data and show 1 corrupted line. */
cpu_draw_buf_.current()->command.array().vertex_len = vertex_len_.load();
cpu_draw_buf_.current()->push_update();
/* Reset buffer. */
cpu_draw_buf_.next()->command.array().vertex_len = 0;
cpu_draw_buf_.next()->push_update();
GPU_storagebuf_bind(*cpu_draw_buf_.current(), DRW_DEBUG_DRAW_SLOT);
GPU_storagebuf_bind(*cpu_draw_buf_.next(), DRW_DEBUG_DRAW_FEEDBACK_SLOT);
GPU_batch_draw_indirect(batch, *cpu_draw_buf_.current(), 0);
GPU_storagebuf_unbind(*cpu_draw_buf_.current());
GPU_storagebuf_unbind(*cpu_draw_buf_.next());
/* Read result of lifetime management. */
cpu_draw_buf_.next()->read();
vertex_len_.store(
min_ii(DRW_DEBUG_DRAW_VERT_MAX, cpu_draw_buf_.next()->command.array().vertex_len));
GPU_debug_group_end();
}
gpu_draw_buf_.swap();
cpu_draw_buf_.swap();
}
void DebugDraw::display_to_view(View &view)
{
/* Display only on the main thread. Avoid concurrent usage of the resource. */
BLI_assert(BLI_thread_is_main());
GPU_debug_group_begin("DebugDraw");
display_lines(view);
GPU_debug_group_end();
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Simple API to draw debug shapes and log in the viewport.
*
* Both CPU and GPU implementation are supported and symmetrical (meaning GPU shader can use it
* too, see common_draw_lib.glsl).
*
* NOTE: CPU logging will overlap GPU logging on screen as it is drawn after.
*/
#pragma once
#include "BLI_math_vector_types.hh"
#include "BLI_mutex.hh"
#include "DNA_object_types.h"
#include "draw_shader_shared.hh"
#include "DRW_gpu_wrapper.hh"
namespace blender::draw {
class View;
/**
* Clear all debug visuals (regardless of visual's lifetime).
*
* Usually called before populating persistent data to override previous visuals.
* Needs an active GPUContext.
*/
void drw_debug_clear();
/* Used for virtually infinite lifetime.
* Useful for debugging render or baking jobs, or non-modal operators. */
constexpr uint drw_debug_persistent_lifetime = ~0u;
/**
* Drawing functions that will draw wire-frames with the given color.
*
* IMPORTANT: `lifetime` is in unit of **display** and not in unit of time.
* One display is defined as one call to `DebugDraw::display_to_view` which happens once
* per 3D viewport if overlays are not turned off.
*
* - The default value of 1 is good for continuous event debugging in one viewport.
* - Above 1 is a good value for infrequent events or to compare continuous event history.
* Alternatively also allows replicating the display to several viewport.
* - drw_debug_persistent_lifetime is a good value for manually triggered event (e.g. an operator).
* It is best to clear the display cache (using `drw_debug_clear`) before adding new persistent
* visuals.
*
* All added debug drawing will be shared across viewports. If lifetime is greater than 1 or if a
* viewport doesn't display the visuals it produced, the visuals will be displayed onto other
* viewport(s).
*
* These functions are threadsafe and can be called concurrently at anytime, even outside the
* UI redraw loop.
*/
void drw_debug_line(float3 v1, float3 v2, float4 color = {1, 0, 0, 1}, uint lifetime = 1);
void drw_debug_polygon(Span<float3> face_verts, float4 color = {1, 0, 0, 1}, uint lifetime = 1);
void drw_debug_bbox(const BoundBox &bbox, float4 color = {1, 0, 0, 1}, uint lifetime = 1);
void drw_debug_sphere(float3 center, float radius, float4 color = {1, 0, 0, 1}, uint lifetime = 1);
/** Same as drw_debug_sphere but with small default radius. */
void drw_debug_point(float3 pos, float rad = 0.01f, float4 col = {1, 0, 0, 1}, uint lifetime = 1);
/** Draw a matrix transform as 3 colored axes. */
void drw_debug_matrix(const float4x4 &m4, uint lifetime = 1);
/** Draw a matrix as a 2 units length bounding box, centered on origin. */
void drw_debug_matrix_as_bbox(const float4x4 &mat, float4 color = {1, 0, 0, 1}, uint lifetime = 1);
class DebugDraw {
private:
using DebugDrawBuf = StorageBuffer<DRWDebugDrawBuffer>;
/**
* Ensure thread-safety when adding geometry to the CPU debug buffer.
* GPU debug buffer currently expects draw submission to be externally synchronized.
*/
std::atomic<int> vertex_len_;
/** Data buffers containing all verts or chars to draw. */
SwapChain<DebugDrawBuf *, 2> cpu_draw_buf_ = {};
SwapChain<DebugDrawBuf *, 2> gpu_draw_buf_ = {};
/** True if the gpu buffer have been requested and may contain data to draw. */
bool gpu_draw_buf_used = false;
/* Reference counter used by GPUContext to allow freeing of DebugDrawBuf before the last
* context is destroyed. */
int ref_count_ = 0;
Mutex ref_count_mutex_;
public:
void reset();
/**
* Draw all debug shapes to the given current view / frame-buffer.
* Draw buffers will be emptied and ready for new debug data.
*/
void display_to_view(View &view);
/** Get GPU debug draw buffer. Can, return nullptr if WITH_DRAW_DEBUG is not enabled. */
gpu::StorageBuf *gpu_draw_buf_get();
void acquire()
{
std::scoped_lock lock(ref_count_mutex_);
ref_count_++;
if (ref_count_ == 1) {
reset();
}
}
void release()
{
std::scoped_lock lock(ref_count_mutex_);
ref_count_--;
if (ref_count_ == 0) {
clear_gpu_data();
}
}
static DebugDraw &get()
{
static DebugDraw module;
return module;
}
void draw_line(float3 v1, float3 v2, uint color, uint lifetime = 1);
static uint color_pack(float4 color);
private:
void display_lines(View &view);
void clear_gpu_data();
};
} // namespace blender::draw

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* List of defines that are shared with the GPUShaderCreateInfos. We do this to avoid
* dragging larger headers into the createInfo pipeline which would cause problems.
*/
#pragma once
/* We target hardware with at least 12 UBO slots (Guaranteed by GL 4.3). */
#define DRW_VIEW_UBO_SLOT 11
#define DRW_VIEW_CULLING_UBO_SLOT 10
#define DRW_OBJ_DATA_INFO_UBO_SLOT 9
#define DRW_LAYER_ATTR_UBO_SLOT 7
/* Slots 0-6 are reserved for engine use. */
/* TODO(fclem): Legacy. To be removed once we remove the old DRW. */
#define DRW_OBJ_INFOS_UBO_SLOT 6
/* TODO(fclem): Remove in favor of engine-side clipping UBO. */
#define DRW_CLIPPING_UBO_SLOT 5
/* We target hardware with at least 12 SSBO slots (NOT Guaranteed by GL 4.3). */
#define DRW_RESOURCE_ID_SLOT 11
#define DRW_OBJ_MAT_SLOT 10
#define DRW_OBJ_INFOS_SLOT 9
#define DRW_OBJ_ATTR_SLOT 8
/* Slots 0-7 are reserved for engine use. */
/* Debug SSBOs are not counted in the limit [12 - 15+]. */
#define DRW_DEBUG_DRAW_SLOT 14
#define DRW_DEBUG_DRAW_FEEDBACK_SLOT 15
#define DRW_COMMAND_GROUP_SIZE 64
#define DRW_FINALIZE_GROUP_SIZE 64
/* Must be multiple of 32. Set to 32 for shader simplicity. */
#define DRW_VISIBILITY_GROUP_SIZE 32
/**
* The maximum of indexable views is dictated by:
* - The UBO limit (16KiB) of the ViewMatrices container.
* - The maximum resource index supported for shaders using multi-view (see DRW_VIEW_SHIFT).
*/
#define DRW_VIEW_MAX 64
#define DRW_POINTCLOUD_STRIP_TILE_SIZE 8
/* TODO(fclem): Move to overlay. */
#define OVERLAY_GLOBALS_SLOT 7

View File

@@ -0,0 +1,610 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*
* GPU fluid drawing functions.
*/
#include <cstring>
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "DNA_fluid_types.h"
#include "DNA_modifier_types.h"
#include "MEM_guardedalloc.h"
#include "BKE_colorband.hh"
#include "IMB_colormanagement.hh"
#include "GPU_texture.hh"
#include "draw_context_private.hh"
#include "draw_common_c.hh" /* Own include. */
#ifdef WITH_FLUID
# include "manta_fluid_API.h"
#endif
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Private API
* \{ */
#ifdef WITH_FLUID
enum {
TFUNC_FLAME_SPECTRUM = 0,
TFUNC_COLOR_RAMP = 1,
};
# define TFUNC_WIDTH 256
static void create_flame_spectrum_texture(float *data)
{
# define FIRE_THRESH 7
# define MAX_FIRE_ALPHA 0.06f
# define FULL_ON_FIRE 100
float *spec_pixels = MEM_new_array_uninitialized<float>(TFUNC_WIDTH * 4 * 16 * 16,
"spec_pixels");
IMB_colormanagement_blackbody_temperature_to_rgb_table(data, TFUNC_WIDTH, 1500, 3000);
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
for (int k = 0; k < TFUNC_WIDTH; k++) {
int index = (j * TFUNC_WIDTH * 16 + i * TFUNC_WIDTH + k) * 4;
if (k >= FIRE_THRESH) {
spec_pixels[index] = (data[k * 4]);
spec_pixels[index + 1] = (data[k * 4 + 1]);
spec_pixels[index + 2] = (data[k * 4 + 2]);
spec_pixels[index + 3] = MAX_FIRE_ALPHA *
((k > FULL_ON_FIRE) ?
1.0f :
(k - FIRE_THRESH) / (float(FULL_ON_FIRE) - FIRE_THRESH));
}
else {
zero_v4(&spec_pixels[index]);
}
}
}
}
memcpy(data, spec_pixels, sizeof(float) * 4 * TFUNC_WIDTH);
MEM_delete(spec_pixels);
# undef FIRE_THRESH
# undef MAX_FIRE_ALPHA
# undef FULL_ON_FIRE
}
static void create_color_ramp(const ColorBand *coba, float *data)
{
for (int i = 0; i < TFUNC_WIDTH; i++) {
BKE_colorband_evaluate(coba, float(i) / TFUNC_WIDTH, &data[i * 4]);
straight_to_premul_v4(&data[i * 4]);
}
}
static gpu::Texture *create_transfer_function(int type, const ColorBand *coba)
{
float *data = MEM_new_array_uninitialized<float>(4 * TFUNC_WIDTH, __func__);
switch (type) {
case TFUNC_FLAME_SPECTRUM:
create_flame_spectrum_texture(data);
break;
case TFUNC_COLOR_RAMP:
create_color_ramp(coba, data);
break;
}
gpu::Texture *tex = GPU_texture_create_1d("transf_func",
TFUNC_WIDTH,
1,
gpu::TextureFormat::SRGBA_8_8_8_8,
GPU_TEXTURE_USAGE_SHADER_READ,
data);
MEM_delete(data);
return tex;
}
static void swizzle_texture_channel_single(gpu::Texture *tex)
{
/* Swizzle texture channels so that we get useful RGBA values when sampling
* a texture with fewer channels, e.g. when using density as color. */
GPU_texture_swizzle_set(tex, "rrr1");
}
static float *rescale_3d(const int dim[3],
const int final_dim[3],
int channels,
const float *fpixels)
{
const uint w = dim[0], h = dim[1], d = dim[2];
const uint fw = final_dim[0], fh = final_dim[1], fd = final_dim[2];
const uint xf = w / fw, yf = h / fh, zf = d / fd;
const uint pixel_count = fw * fh * fd;
float *nfpixels = MEM_new_array_uninitialized<float>(channels * pixel_count, __func__);
if (nfpixels) {
printf("Performance: You need to scale a 3D texture, feel the pain!\n");
for (uint k = 0; k < fd; k++) {
for (uint j = 0; j < fh; j++) {
for (uint i = 0; i < fw; i++) {
/* Obviously doing nearest filtering here,
* it's going to be slow in any case, let's not make it worse. */
float xb = i * xf;
float yb = j * yf;
float zb = k * zf;
uint offset = k * (fw * fh) + i * fh + j;
uint offset_orig = (zb) * (w * h) + (xb)*h + (yb);
if (channels == 4) {
nfpixels[offset * 4] = fpixels[offset_orig * 4];
nfpixels[offset * 4 + 1] = fpixels[offset_orig * 4 + 1];
nfpixels[offset * 4 + 2] = fpixels[offset_orig * 4 + 2];
nfpixels[offset * 4 + 3] = fpixels[offset_orig * 4 + 3];
}
else if (channels == 1) {
nfpixels[offset] = fpixels[offset_orig];
}
else {
BLI_assert(0);
}
}
}
}
}
return nfpixels;
}
/* Will resize input to fit GL system limits. */
static gpu::Texture *create_volume_texture(const int dim[3],
gpu::TextureFormat texture_format,
eGPUDataFormat data_format,
const void *data)
{
gpu::Texture *tex = nullptr;
int3 final_dim = {UNPACK3(dim)};
if (data == nullptr) {
return nullptr;
}
while (true) {
tex = GPU_texture_create_3d(
"volume", UNPACK3(final_dim), 1, texture_format, GPU_TEXTURE_USAGE_SHADER_READ, nullptr);
if (tex != nullptr) {
break;
}
if (final_dim[0] == 1 && final_dim[1] == 1 && final_dim[2] == 1) {
break;
}
for (int i = 0; i < 3; i++) {
final_dim[i] = max_ii(1, final_dim[i] / 2);
}
}
if (tex == nullptr) {
printf("Error: Could not create 3D texture.\n");
tex = GPU_texture_create_error(3, false);
}
else if (int3(dim) == final_dim) {
/* No need to resize, just upload the data. */
GPU_texture_update_sub(tex, data_format, data, 0, 0, 0, UNPACK3(final_dim));
}
else if (data_format != GPU_DATA_FLOAT) {
printf("Error: Could not allocate 3D texture and not attempting to rescale non-float data.\n");
tex = GPU_texture_create_error(3, false);
}
else {
/* We need to resize the input. */
int channels = ELEM(texture_format,
gpu::TextureFormat::UNORM_8,
gpu::TextureFormat::SFLOAT_16,
gpu::TextureFormat::SFLOAT_32) ?
1 :
4;
float *rescaled_data = rescale_3d(dim, final_dim, channels, static_cast<const float *>(data));
if (rescaled_data) {
GPU_texture_update_sub(tex, GPU_DATA_FLOAT, rescaled_data, 0, 0, 0, UNPACK3(final_dim));
MEM_delete(rescaled_data);
}
else {
printf("Error: Could not allocate rescaled 3d texture!\n");
GPU_texture_free(tex);
tex = GPU_texture_create_error(3, false);
}
}
return tex;
}
static gpu::Texture *create_field_texture(FluidDomainSettings *fds, bool single_precision)
{
void *field = nullptr;
eGPUDataFormat data_format = GPU_DATA_FLOAT;
gpu::TextureFormat texture_format = gpu::TextureFormat::UNORM_8;
if (single_precision) {
texture_format = gpu::TextureFormat::SFLOAT_32;
}
switch (fds->coba_field) {
case FLUID_DOMAIN_FIELD_DENSITY:
field = manta_smoke_get_density(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_HEAT:
field = manta_smoke_get_heat(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_FUEL:
field = manta_smoke_get_fuel(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_REACT:
field = manta_smoke_get_react(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_FLAME:
field = manta_smoke_get_flame(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_VELOCITY_X:
field = manta_get_velocity_x(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_VELOCITY_Y:
field = manta_get_velocity_y(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_VELOCITY_Z:
field = manta_get_velocity_z(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_COLOR_R:
field = manta_smoke_get_color_r(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_COLOR_G:
field = manta_smoke_get_color_g(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_COLOR_B:
field = manta_smoke_get_color_b(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_FORCE_X:
field = manta_get_force_x(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_FORCE_Y:
field = manta_get_force_y(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_FORCE_Z:
field = manta_get_force_z(fds->fluid);
break;
case FLUID_DOMAIN_FIELD_PHI:
field = manta_get_phi(fds->fluid);
texture_format = gpu::TextureFormat::SFLOAT_16;
break;
case FLUID_DOMAIN_FIELD_PHI_IN:
field = manta_get_phi_in(fds->fluid);
texture_format = gpu::TextureFormat::SFLOAT_16;
break;
case FLUID_DOMAIN_FIELD_PHI_OUT:
field = manta_get_phiout_in(fds->fluid);
texture_format = gpu::TextureFormat::SFLOAT_16;
break;
case FLUID_DOMAIN_FIELD_PHI_OBSTACLE:
field = manta_get_phiobs_in(fds->fluid);
texture_format = gpu::TextureFormat::SFLOAT_16;
break;
case FLUID_DOMAIN_FIELD_FLAGS:
field = manta_smoke_get_flags(fds->fluid);
data_format = GPU_DATA_INT;
texture_format = gpu::TextureFormat::UINT_8;
break;
case FLUID_DOMAIN_FIELD_PRESSURE:
field = manta_get_pressure(fds->fluid);
texture_format = gpu::TextureFormat::SFLOAT_16;
break;
default:
return nullptr;
}
if (field == nullptr) {
return nullptr;
}
gpu::Texture *tex = create_volume_texture(fds->res, texture_format, data_format, field);
swizzle_texture_channel_single(tex);
return tex;
}
static gpu::Texture *create_density_texture(FluidDomainSettings *fds, int highres)
{
int *dim = (highres) ? fds->res_noise : fds->res;
float *data;
if (highres) {
data = manta_noise_get_density(fds->fluid);
}
else {
data = manta_smoke_get_density(fds->fluid);
}
if (data == nullptr) {
return nullptr;
}
gpu::Texture *tex = create_volume_texture(
dim, gpu::TextureFormat::UNORM_8, GPU_DATA_FLOAT, data);
swizzle_texture_channel_single(tex);
return tex;
}
static gpu::Texture *create_color_texture(FluidDomainSettings *fds, int highres)
{
const bool has_color = (highres) ? manta_noise_has_colors(fds->fluid) :
manta_smoke_has_colors(fds->fluid);
if (!has_color) {
return nullptr;
}
int cell_count = (highres) ? manta_noise_get_cells(fds->fluid) : fds->total_cells;
int *dim = (highres) ? fds->res_noise : fds->res;
float *data = MEM_new_array_zeroed<float>(cell_count * 4, "smokeColorTexture");
if (data == nullptr) {
return nullptr;
}
if (highres) {
manta_noise_get_rgba(fds->fluid, data, 0);
}
else {
manta_smoke_get_rgba(fds->fluid, data, 0);
}
gpu::Texture *tex = create_volume_texture(
dim, gpu::TextureFormat::UNORM_8_8_8_8, GPU_DATA_FLOAT, data);
MEM_delete(data);
return tex;
}
static gpu::Texture *create_flame_texture(FluidDomainSettings *fds, int highres)
{
float *source = nullptr;
const bool has_fuel = (highres) ? manta_noise_has_fuel(fds->fluid) :
manta_smoke_has_fuel(fds->fluid);
int *dim = (highres) ? fds->res_noise : fds->res;
if (!has_fuel) {
return nullptr;
}
if (highres) {
source = manta_noise_get_flame(fds->fluid);
}
else {
source = manta_smoke_get_flame(fds->fluid);
}
gpu::Texture *tex = create_volume_texture(
dim, gpu::TextureFormat::UNORM_8, GPU_DATA_FLOAT, source);
swizzle_texture_channel_single(tex);
return tex;
}
static bool get_smoke_velocity_field(FluidDomainSettings *fds,
float **r_velocity_x,
float **r_velocity_y,
float **r_velocity_z)
{
const char vector_field = fds->vector_field;
switch ((FLUID_DisplayVectorField)vector_field) {
case FLUID_DOMAIN_VECTOR_FIELD_VELOCITY:
*r_velocity_x = manta_get_velocity_x(fds->fluid);
*r_velocity_y = manta_get_velocity_y(fds->fluid);
*r_velocity_z = manta_get_velocity_z(fds->fluid);
break;
case FLUID_DOMAIN_VECTOR_FIELD_GUIDE_VELOCITY:
*r_velocity_x = manta_get_guide_velocity_x(fds->fluid);
*r_velocity_y = manta_get_guide_velocity_y(fds->fluid);
*r_velocity_z = manta_get_guide_velocity_z(fds->fluid);
break;
case FLUID_DOMAIN_VECTOR_FIELD_FORCE:
*r_velocity_x = manta_get_force_x(fds->fluid);
*r_velocity_y = manta_get_force_y(fds->fluid);
*r_velocity_z = manta_get_force_z(fds->fluid);
break;
}
return *r_velocity_x && *r_velocity_y && *r_velocity_z;
}
#endif /* WITH_FLUID */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
void DRW_smoke_ensure_coba_field(FluidModifierData *fmd)
{
#ifndef WITH_FLUID
UNUSED_VARS(fmd);
#else
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
FluidDomainSettings *fds = fmd->domain;
if (!fds->tex_field) {
fds->tex_field = create_field_texture(fds, false);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_field));
}
if (!fds->tex_coba && !ELEM(fds->coba_field,
FLUID_DOMAIN_FIELD_PHI,
FLUID_DOMAIN_FIELD_PHI_IN,
FLUID_DOMAIN_FIELD_PHI_OUT,
FLUID_DOMAIN_FIELD_PHI_OBSTACLE,
FLUID_DOMAIN_FIELD_FLAGS,
FLUID_DOMAIN_FIELD_PRESSURE))
{
fds->tex_coba = create_transfer_function(TFUNC_COLOR_RAMP, fds->coba);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_coba));
}
}
#endif
}
void DRW_smoke_ensure(FluidModifierData *fmd, int highres)
{
#ifndef WITH_FLUID
UNUSED_VARS(fmd, highres);
#else
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
FluidDomainSettings *fds = fmd->domain;
if (!fds->tex_density) {
fds->tex_density = create_density_texture(fds, highres);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_density));
}
if (!fds->tex_color) {
fds->tex_color = create_color_texture(fds, highres);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_color));
}
if (!fds->tex_flame) {
fds->tex_flame = create_flame_texture(fds, highres);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_flame));
}
if (!fds->tex_flame_coba && fds->tex_flame) {
fds->tex_flame_coba = create_transfer_function(TFUNC_FLAME_SPECTRUM, nullptr);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_flame_coba));
}
if (!fds->tex_shadow) {
fds->tex_shadow = create_volume_texture(fds->res,
gpu::TextureFormat::UNORM_8,
GPU_DATA_FLOAT,
manta_smoke_get_shadow(fds->fluid));
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_shadow));
}
}
#endif /* WITH_FLUID */
}
void DRW_smoke_ensure_velocity(FluidModifierData *fmd)
{
#ifndef WITH_FLUID
UNUSED_VARS(fmd);
#else
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
FluidDomainSettings *fds = fmd->domain;
float *vel_x = nullptr, *vel_y = nullptr, *vel_z = nullptr;
if (!get_smoke_velocity_field(fds, &vel_x, &vel_y, &vel_z)) {
fds->vector_field = FLUID_DOMAIN_VECTOR_FIELD_VELOCITY;
get_smoke_velocity_field(fds, &vel_x, &vel_y, &vel_z);
}
if (ELEM(nullptr, vel_x, vel_y, vel_z)) {
return;
}
if (!fds->tex_velocity_x) {
fds->tex_velocity_x = GPU_texture_create_3d("velx",
UNPACK3(fds->res),
1,
gpu::TextureFormat::SFLOAT_16,
GPU_TEXTURE_USAGE_SHADER_READ,
vel_x);
fds->tex_velocity_y = GPU_texture_create_3d("vely",
UNPACK3(fds->res),
1,
gpu::TextureFormat::SFLOAT_16,
GPU_TEXTURE_USAGE_SHADER_READ,
vel_y);
fds->tex_velocity_z = GPU_texture_create_3d("velz",
UNPACK3(fds->res),
1,
gpu::TextureFormat::SFLOAT_16,
GPU_TEXTURE_USAGE_SHADER_READ,
vel_z);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_velocity_x));
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_velocity_y));
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_velocity_z));
}
}
#endif /* WITH_FLUID */
}
void DRW_fluid_ensure_flags(FluidModifierData *fmd)
{
#ifndef WITH_FLUID
UNUSED_VARS(fmd);
#else
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
FluidDomainSettings *fds = fmd->domain;
if (!fds->tex_flags) {
fds->tex_flags = create_volume_texture(
fds->res, gpu::TextureFormat::UINT_8, GPU_DATA_INT, manta_smoke_get_flags(fds->fluid));
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_flags));
swizzle_texture_channel_single(fds->tex_flags);
}
}
#endif /* WITH_FLUID */
}
void DRW_fluid_ensure_range_field(FluidModifierData *fmd)
{
#ifndef WITH_FLUID
UNUSED_VARS(fmd);
#else
if (fmd->type & MOD_FLUID_TYPE_DOMAIN) {
FluidDomainSettings *fds = fmd->domain;
if (!fds->tex_range_field) {
fds->tex_range_field = create_field_texture(fds, true);
BLI_addtail(&drw_get().data->smoke_textures, BLI_genericNodeN(&fds->tex_range_field));
}
}
#endif /* WITH_FLUID */
}
void DRW_smoke_begin_sync(DRWData *drw_data)
{
if (drw_data->smoke_textures.is_empty()) {
return;
}
/* See workaround at blender::eevee::Instance::render_sample
* Calling exit here as sync can happen multiple times when shaders are still being compiled
* during image rendering. */
DRW_smoke_exit(drw_data);
}
void DRW_smoke_exit(DRWData *drw_data)
{
/* Free Smoke Textures after rendering */
/* XXX This is a waste of processing and GPU bandwidth if nothing
* is updated. But the problem is since Textures are stored in the
* modifier we don't want them to take precious VRAM if the
* modifier is not used for display. We should share them for
* all viewport in a redraw at least. */
for (LinkData &link : drw_data->smoke_textures) {
GPU_TEXTURE_FREE_SAFE(*(gpu::Texture **)link.data);
}
drw_data->smoke_textures.free_no_destruct();
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,455 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_global.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "GPU_state.hh"
#include "RE_engine.h"
#include "RE_pipeline.h"
#include "DRW_engine.hh"
#include "DRW_render.hh"
#include "WM_api.hh"
#include "wm_window.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Submission critical section
*
* The usage of gpu::Shader objects is currently not thread safe. Since they are shared resources
* between render engine instances, we cannot allow pass submissions in a concurrent manner.
* \{ */
static TicketMutex *draw_mutex = nullptr;
static TicketMutex *submission_mutex = nullptr;
void DRW_mutexes_init()
{
draw_mutex = BLI_ticket_mutex_alloc();
submission_mutex = BLI_ticket_mutex_alloc();
}
void DRW_mutexes_exit()
{
BLI_ticket_mutex_free(draw_mutex);
BLI_ticket_mutex_free(submission_mutex);
}
void DRW_lock_start()
{
bool locked = BLI_ticket_mutex_lock_check_recursive(draw_mutex);
BLI_assert(locked);
UNUSED_VARS_NDEBUG(locked);
}
void DRW_lock_end()
{
BLI_ticket_mutex_unlock(draw_mutex);
}
void DRW_submission_start()
{
bool locked = BLI_ticket_mutex_lock_check_recursive(submission_mutex);
BLI_assert(locked);
UNUSED_VARS_NDEBUG(locked);
GPU_render_begin();
}
void DRW_submission_end()
{
GPU_render_end();
BLI_ticket_mutex_unlock(submission_mutex);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name ContextShared
*
* \{ */
/* Context that can be shared across threads. Usage is guarded by a ticket mutex.
* Should eventually be moved to GPU module after we get rid of the WM calls. */
class ContextShared {
/* Should be private but needs to be public for XR workaround. */
public:
TicketMutex *mutex_ = nullptr;
/** Unique ghost context used by Viewports. */
GHOST_IContext *system_gpu_context_ = nullptr;
/** GPUContext associated to the system_gpu_context. */
GPUContext *blender_gpu_context_ = nullptr;
/* NOTE: This changes the active context. */
ContextShared()
{
mutex_ = BLI_ticket_mutex_alloc();
system_gpu_context_ = WM_system_gpu_context_create();
WM_system_gpu_context_activate(system_gpu_context_);
blender_gpu_context_ = GPU_context_create(nullptr, system_gpu_context_);
}
~ContextShared()
{
WM_system_gpu_context_activate(system_gpu_context_);
GPU_context_active_set(blender_gpu_context_);
GPU_context_discard(blender_gpu_context_);
WM_system_gpu_context_dispose(system_gpu_context_);
BLI_ticket_mutex_free(mutex_);
}
void enable()
{
DRW_lock_start();
/* IMPORTANT: We don't support immediate mode in render mode!
* This shall remain in effect until immediate mode supports
* multiple threads. */
BLI_ticket_mutex_lock(mutex_);
GPU_render_begin();
WM_system_gpu_context_activate(system_gpu_context_);
GPU_context_active_set(blender_gpu_context_);
GPU_context_begin_frame(blender_gpu_context_);
}
bool is_enabled()
{
return blender_gpu_context_ == GPU_context_active_get();
}
/* Restore window drawable after disabling if restore is true. */
void disable(bool restore = false)
{
GPU_context_end_frame(blender_gpu_context_);
if (BLI_thread_is_main() && restore) {
wm_window_reset_drawable();
}
else {
WM_system_gpu_context_release(system_gpu_context_);
GPU_context_active_set(nullptr);
}
/* Render boundaries are opened and closed here as this may be
* called outside of an existing render loop. */
GPU_render_end();
BLI_ticket_mutex_unlock(mutex_);
DRW_lock_end();
}
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name GPU & System Context
*
* A global GPUContext is used for rendering every viewports (even on different windows).
* This is because some resources cannot be shared between contexts (GPUFramebuffers, GPUBatch).
* \{ */
/** Unique context used by Viewports. */
static ContextShared *viewport_context = nullptr;
/** Unique context used by Preview jobs. */
static ContextShared *preview_context = nullptr;
void DRW_gpu_context_create()
{
BLI_assert(viewport_context == nullptr); /* Ensure it's called once */
DRW_mutexes_init();
viewport_context = MEM_new<ContextShared>(__func__);
preview_context = MEM_new<ContextShared>(__func__);
viewport_context->enable();
}
void DRW_gpu_context_destroy()
{
BLI_assert(BLI_thread_is_main());
if (viewport_context == nullptr) {
return;
}
DRW_mutexes_exit();
MEM_SAFE_DELETE(viewport_context);
MEM_SAFE_DELETE(preview_context);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Draw GPU Context
* \{ */
void DRW_gpu_context_enable_ex(bool /*restore*/)
{
if (viewport_context == nullptr) {
return;
}
viewport_context->enable();
}
void DRW_gpu_context_disable_ex(bool restore)
{
if (viewport_context == nullptr) {
return;
}
viewport_context->disable(restore);
}
static void drw_gpu_preview_context_enable()
{
if (preview_context == nullptr) {
return;
}
preview_context->enable();
}
static void drw_gpu_preview_context_disable()
{
if (preview_context == nullptr) {
return;
}
preview_context->disable();
}
void DRW_gpu_context_enable()
{
/* TODO: should be replace by a more elegant alternative. */
if (G.background && viewport_context == nullptr) {
WM_init_gpu();
}
DRW_gpu_context_enable_ex(true);
}
bool DRW_gpu_context_try_enable()
{
if (viewport_context == nullptr) {
return false;
}
DRW_gpu_context_enable_ex(true);
return true;
}
bool DRW_gpu_context_is_enabled()
{
return viewport_context && viewport_context->is_enabled();
}
void DRW_gpu_context_disable()
{
DRW_gpu_context_disable_ex(true);
}
void DRW_system_gpu_render_context_enable(GHOST_IContext *re_system_gpu_context)
{
/* If thread is main you should use DRW_gpu_context_enable(). */
BLI_assert(!BLI_thread_is_main());
DRW_lock_start();
WM_system_gpu_context_activate(re_system_gpu_context);
}
void DRW_system_gpu_render_context_disable(GHOST_IContext *re_system_gpu_context)
{
WM_system_gpu_context_release(re_system_gpu_context);
DRW_lock_end();
}
void DRW_blender_gpu_render_context_enable(void *re_gpu_context)
{
/* If thread is main you should use DRW_gpu_context_enable(). */
BLI_assert(!BLI_thread_is_main());
GPU_context_active_set(static_cast<GPUContext *>(re_gpu_context));
}
void DRW_blender_gpu_render_context_disable(void * /*re_gpu_context*/)
{
GPU_flush();
GPU_context_active_set(nullptr);
}
void DRW_render_context_enable(Render *render)
{
if (G.background && viewport_context == nullptr) {
WM_init_gpu();
}
GPU_render_begin();
if (GPU_use_main_context_workaround()) {
GPU_context_main_lock();
DRW_gpu_context_enable();
return;
}
GHOST_IContext *re_viewport_system_gpu_context = RE_system_gpu_context_get(render);
/* Changing Context */
if (re_viewport_system_gpu_context != nullptr) {
DRW_system_gpu_render_context_enable(re_viewport_system_gpu_context);
/* We need to query gpu context after a gl context has been bound. */
void *re_viewport_context = RE_blender_gpu_context_ensure(render);
DRW_blender_gpu_render_context_enable(re_viewport_context);
}
else {
drw_gpu_preview_context_enable();
}
}
void DRW_render_context_disable(Render *render)
{
if (GPU_use_main_context_workaround()) {
DRW_gpu_context_disable();
GPU_render_end();
GPU_context_main_unlock();
return;
}
GHOST_IContext *re_viewport_system_gpu_context = RE_system_gpu_context_get(render);
if (re_viewport_system_gpu_context != nullptr) {
void *re_viewport_context = RE_blender_gpu_context_ensure(render);
/* GPU rendering may occur during context disable. */
DRW_blender_gpu_render_context_disable(re_viewport_context);
GPU_render_end();
DRW_system_gpu_render_context_disable(re_viewport_system_gpu_context);
}
else {
/* Usually the case for a preview job. The `Render` is created inside the render thread which
* is too late to create a GPU context. */
drw_gpu_preview_context_disable();
GPU_render_end();
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name XR
* \{ */
#ifdef WITH_XR_OPENXR
GHOST_IContext *DRW_system_gpu_context_get()
{
/* XXX: There should really be no such getter, but for VR we currently can't easily avoid it.
* OpenXR needs some low level info for the GPU context that will be used for submitting the
* final frame-buffer. VR could in theory create its own context, but that would mean we have to
* switch to it just to submit the final frame, which has notable performance impact.
*
* We could "inject" a context through DRW_system_gpu_render_context_enable(), but that would
* have to work from the main thread, which is tricky to get working too. The preferable solution
* would be using a separate thread for VR drawing where a single context can stay active. */
return viewport_context->system_gpu_context_;
}
void *DRW_xr_blender_gpu_context_get()
{
/* XXX: See comment on #DRW_system_gpu_context_get(). */
return viewport_context->blender_gpu_context_;
}
void DRW_xr_drawing_begin()
{
/* XXX: See comment on #DRW_system_gpu_context_get(). */
DRW_lock_start();
BLI_ticket_mutex_lock(viewport_context->mutex_);
}
void DRW_xr_drawing_end()
{
/* XXX: See comment on #DRW_system_gpu_context_get(). */
BLI_ticket_mutex_unlock(viewport_context->mutex_);
DRW_lock_end();
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Draw manager context release/activation
*
* These functions are used in cases when an GPU context creation is needed during the draw.
* This happens, for example, when an external engine needs to create its own GPU context from
* the engine initialization.
*
* Example of context creation:
*
* const bool drw_state = DRW_gpu_context_release();
* viewport_system_gpu_context = WM_system_gpu_context_create();
* DRW_gpu_context_activate(drw_state);
*
* Example of context destruction:
*
* const bool drw_state = DRW_gpu_context_release();
* WM_system_gpu_context_activate(viewport_system_gpu_context);
* WM_system_gpu_context_dispose(viewport_system_gpu_context);
* DRW_gpu_context_activate(drw_state);
*
*
* NOTE: Will only perform context modification when on main thread. This way these functions can
* be used in an engine without check on whether it is a draw manager which manages GPU context
* on the current thread. The downside of this is that if the engine performs GPU creation from
* a non-main thread, that thread is supposed to not have GPU context ever bound by Blender.
*
* \{ */
bool DRW_gpu_context_release()
{
if (!BLI_thread_is_main()) {
return false;
}
if (GPU_context_active_get() != viewport_context->blender_gpu_context_) {
/* Context release is requested from the outside of the draw manager main draw loop, indicate
* this to the `DRW_gpu_context_activate()` so that it restores drawable of the window.
*/
return false;
}
GPU_context_active_set(nullptr);
WM_system_gpu_context_release(viewport_context->system_gpu_context_);
return true;
}
void DRW_gpu_context_activate(bool drw_state)
{
if (!BLI_thread_is_main()) {
return;
}
if (drw_state) {
WM_system_gpu_context_activate(viewport_context->system_gpu_context_);
GPU_context_active_set(viewport_context->blender_gpu_context_);
}
else {
wm_window_reset_drawable();
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,123 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Contains procedural GPU hair drawing methods.
*/
#include "BKE_customdata.hh"
#include "DNA_modifier_types.h"
#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "DRW_render.hh"
#include "GPU_batch.hh"
#include "GPU_material.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
#include "draw_common_c.hh"
#include "draw_context_private.hh"
#include "draw_hair_private.hh"
#include "draw_manager.hh"
#include "draw_shader_shared.hh"
/* New Draw Manager. */
#include "draw_common.hh"
namespace blender::draw {
gpu::VertBuf *hair_pos_buffer_get(Scene *scene,
Object *object,
ParticleSystem *psys,
ModifierData *md)
{
/* TODO(fclem): Remove Global access. */
CurvesModule &module = *drw_get().data->curves_module;
drw_particle_update_ptcache(object, psys);
ParticleDrawSource source = drw_particle_get_hair_source(
object, psys, md, nullptr, scene->r.hair_subdiv);
CurvesEvalCache &cache = hair_particle_get_eval_cache(source);
cache.ensure_positions(module, source);
return cache.evaluated_pos_rad_buf.get();
}
template<typename PassT>
gpu::Batch *hair_sub_pass_setup_implementation(PassT &sub_ps,
const Scene *scene,
const ObjectRef &ob_ref,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material)
{
/** NOTE: This still relies on the old DRW_hair implementation. */
Object *object = ob_ref.object;
drw_particle_update_ptcache(object, psys);
ParticleDrawSource source = drw_particle_get_hair_source(
object, psys, md, nullptr, scene->r.hair_subdiv);
CurvesEvalCache &cache = hair_particle_get_eval_cache(source);
const int face_per_segment = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 0 :
(scene->r.hair_type == SCE_HAIR_SHAPE_CYLINDER) ? 3 :
1;
if (source.evaluated_points_num() == 0) {
/* Nothing to draw. Just return an empty drawcall that will be skipped. */
bool unused_error;
return cache.batch_get(0, 0, face_per_segment, false, unused_error);
}
/* TODO(fclem): Remove Global access. */
CurvesModule &module = *drw_get().data->curves_module;
cache.ensure_positions(module, source);
cache.ensure_attributes(module, source, gpu_material);
gpu::VertBufPtr &indirection_buf = cache.indirection_buf_get(module, source, face_per_segment);
{
ParticleSystemModifierData *psmd = reinterpret_cast<ParticleSystemModifierData *>(source.md);
Mesh &mesh = *psmd->mesh_final;
const StringRef active_uv = mesh.active_uv_map_name();
curves_bind_resources(
sub_ps, module, cache, face_per_segment, gpu_material, indirection_buf, active_uv);
}
bool unused_error;
return cache.batch_get(
source.evaluated_points_num(), source.curves_num(), face_per_segment, false, unused_error);
}
gpu::Batch *hair_sub_pass_setup(PassMain::Sub &sub_ps,
const Scene *scene,
const ObjectRef &ob_ref,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material)
{
return hair_sub_pass_setup_implementation(sub_ps, scene, ob_ref, psys, md, gpu_material);
}
gpu::Batch *hair_sub_pass_setup(PassSimple::Sub &sub_ps,
const Scene *scene,
const ObjectRef &ob_ref,
ParticleSystem *psys,
ModifierData *md,
GPUMaterial *gpu_material)
{
return hair_sub_pass_setup_implementation(sub_ps, scene, ob_ref, psys, md, gpu_material);
}
} // namespace blender::draw

View File

@@ -0,0 +1,159 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "draw_curves_private.hh"
#include "draw_pass.hh"
struct ParticleHairCache;
namespace blender {
namespace bke {
class CurvesGeometry;
}
namespace draw {
struct CurvesEvalCache;
class CurveRefinePass : public PassSimple {
public:
CurveRefinePass(const char *name) : PassSimple(name) {};
};
using CurvesInfosBuf = UniformBuffer<CurvesInfos>;
struct CurvesUniformBufPool {
Vector<std::unique_ptr<CurvesInfosBuf>> ubos;
int used = 0;
void reset()
{
used = 0;
/* Allocate dummy. */
alloc();
ubos.first()->push_update();
}
CurvesInfosBuf &dummy_get()
{
return *ubos.first();
}
CurvesInfosBuf &alloc();
};
struct CurvesModule {
CurvesUniformBufPool ubo_pool;
CurveRefinePass refine = {"CurvesEvalPass"};
/* Contains all transient input buffers contained inside `refine`.
* Cleared after update. */
Vector<gpu::VertBufPtr> transient_buffers;
gpu::VertBuf *dummy_vbo = drw_curves_ensure_dummy_vbo();
~CurvesModule()
{
GPU_VERTBUF_DISCARD_SAFE(dummy_vbo);
}
void init()
{
ubo_pool.reset();
refine.init();
refine.state_set(DRW_STATE_NO_DRAW);
}
/* Record evaluation inside `refine`.
* Output will be ready once `refine` pass has been submitted. */
void evaluate_curve_attribute(bool has_catmull,
bool has_bezier,
bool has_poly,
bool has_nurbs,
bool has_cyclic,
int curve_count,
CurvesEvalCache &cache,
CurvesEvalShader shader_type,
gpu::VertBufPtr input_buf,
gpu::VertBufPtr &output_buf,
/* For radius during position evaluation. */
gpu::VertBuf *input2_buf = nullptr,
/* For baking a transform during position evaluation. */
float4x4 transform = float4x4::identity());
void evaluate_positions(bool has_catmull,
bool has_bezier,
bool has_poly,
bool has_nurbs,
bool has_cyclic,
int curve_count,
CurvesEvalCache &cache,
gpu::VertBufPtr input_pos_buf,
gpu::VertBufPtr input_rad_buf,
gpu::VertBufPtr &output_pos_buf,
float4x4 transform = float4x4::identity())
{
evaluate_curve_attribute(has_catmull,
has_bezier,
has_poly,
has_nurbs,
has_cyclic,
curve_count,
cache,
CURVES_EVAL_POSITION,
std::move(input_pos_buf),
output_pos_buf,
/* Transfer ownership through optional argument. */
input_rad_buf.release(),
transform);
}
void evaluate_curve_length_intercept(bool has_cyclic, int curve_count, CurvesEvalCache &cache);
gpu::VertBufPtr evaluate_topology_indirection(const int curve_count,
const int point_count,
CurvesEvalCache &cache,
bool is_ribbon,
bool has_cyclic);
private:
gpu::VertBuf *drw_curves_ensure_dummy_vbo();
void dispatch(int curve_count, PassSimple::Sub &pass);
};
} // namespace draw
#define MAX_LAYER_NAME_CT 4 /* `u0123456789, u, au, a0123456789`. */
#define MAX_LAYER_NAME_LEN (GPU_MAX_SAFE_ATTR_NAME + 2)
#define MAX_THICKRES 2 /* see eHairType */
#define MAX_HAIR_SUBDIV 4 /* see hair_subdiv rna */
enum ParticleRefineShader {
PART_REFINE_CATMULL_ROM = 0,
PART_REFINE_MAX_SHADER,
};
struct ModifierData;
struct Object;
struct ParticleSystem;
namespace draw {
void drw_particle_update_ptcache(Object *object_eval, ParticleSystem *psys);
ParticleDrawSource drw_particle_get_hair_source(Object *object,
ParticleSystem *psys,
ModifierData *md,
PTCacheEdit *edit,
int additional_subdivision);
CurvesEvalCache &hair_particle_get_eval_cache(ParticleDrawSource &src);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,550 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup draw
*
* A unique identifier for each object component.
* It is used to access each component data such as matrices and object attributes.
* It is valid only for the current draw, it is not persistent.
*
* The most significant bit is used to encode if the object needs to invert the front face winding
* because of its object matrix handedness. This is handy because this means sorting inside
* #DrawGroup command will put all inverted commands last.
*
* Default value of 0 points toward an non-cull-able object with unit bounding box centered at
* the origin.
*/
#include "BKE_duplilist.hh"
#include "draw_shader_shared.hh"
/* random */
#include "BLI_hash.h"
/* find_rgba_attribute */
#include "GPU_material.hh"
/* particles_matrix */
#include "BLI_math_matrix.hh"
#include "DNA_collection_types.h"
/* parent_is_in_edit_paint_mode */
#include "BKE_context.hh"
#include "BKE_paint.hh"
#include "DNA_layer_types.h"
#include "DRW_render.hh"
/* ObjectKey */
#include "DEG_depsgraph_query.hh"
namespace blender {
struct DupliCacheManager;
namespace draw {
/**
* Index for getting a specific resource from the Draw Manager resource arrays.
* (e.g. object matrices)
* Last bit contains handedness.
*
* NOTE: From the draw_pass and draw_command perspective, the 0 index is still valid and points to
* default initialized Manager resources. Valid ResourceHandles start at index 1.
*/
struct ResourceID {
uint32_t raw;
ResourceID() = default;
ResourceID(uint raw_) : raw(raw_) {};
ResourceID(uint index, bool inverted_handedness)
{
raw = index;
SET_FLAG_FROM_TEST(raw, inverted_handedness, 0x80000000u);
}
bool has_inverted_handedness() const
{
return (raw & 0x80000000u) != 0;
}
uint index() const
{
return (raw & 0x7FFFFFFFu);
}
};
/**
* Refers to a range of contiguous indices in the Draw Manager resource arrays.
* Typically used to render instances of an object, but can represent a single instance too.
* The associated objects must share handedness and state so they can be rendered together.
*/
struct ResourceIDRange {
/* First handle in the range. */
ResourceID first = 0;
/* Number of handles in the range. */
uint32_t count = 1;
ResourceIDRange() = default;
ResourceIDRange(ResourceID id) : first(id) {}
ResourceIDRange(ResourceID first_id, uint len) : first(first_id), count(len) {}
bool has_inverted_handedness() const
{
return first.has_inverted_handedness();
}
IndexRange id_range() const
{
BLI_assert(count > 0);
BLI_assert(first.raw != 0 || count == 1);
return {first.raw, count};
}
};
/**
* Safety wrapper around ResourceID, meant to be used by engine code.
* Valid handles can only be created by the Draw Manager.
*
* ResourceHandleRange is usually preferred over this class.
* ResourceHandle should only be used in paths where handling objects in batches is not possible.
*/
class ResourceHandle {
friend class Manager;
friend class ResourceHandleRange;
ResourceID id_ = {};
ResourceHandle(uint raw) : id_(raw) {}
ResourceHandle(uint index, bool inverted_handedness) : id_(index, inverted_handedness) {}
public:
ResourceHandle() = default;
bool is_valid() const
{
return id_.raw != 0;
}
bool has_inverted_handedness() const
{
return id_.has_inverted_handedness();
}
uint raw() const
{
return id_.raw;
}
uint index() const
{
return id_.index();
}
operator ResourceID() const
{
BLI_assert(is_valid());
return id_;
}
};
/**
* Safety wrapper around ResourceIDRange, meant to be used by engine code.
* Valid handles can only be created by the Draw Manager.
*/
class ResourceHandleRange {
friend class Manager;
friend class ResourceHandle;
ResourceIDRange id_ = {};
ResourceHandleRange(ResourceHandle first_handle, uint len) : id_(first_handle.id_, len) {}
public:
ResourceHandleRange() = default;
ResourceHandleRange(ResourceHandle handle) : id_(handle.id_) {}
bool is_valid() const
{
return id_.first.raw != 0;
}
bool has_inverted_handedness() const
{
return id_.has_inverted_handedness();
}
IndexRange id_range() const
{
return id_.id_range();
}
operator ResourceIDRange() const
{
BLI_assert(is_valid());
return id_;
}
/* Returns a single handle within this range.
* May be required for passes that require per-instance setups,
* so their drawing can't be batched into a single draw call. */
ResourceHandle sub_handle(int index) const
{
BLI_assert(index < id_.count);
return ResourceHandle(id_.first.index() + index, id_.first.has_inverted_handedness());
}
operator ResourceHandle() const
{
BLI_assert(id_.count == 1);
return ResourceHandle(id_.first.raw);
}
uint raw() const
{
BLI_assert(id_.count == 1);
return id_.first.raw;
}
uint index() const
{
BLI_assert(id_.count == 1);
return id_.first.index();
}
};
/* TODO(fclem): Move to somewhere more appropriated after cleaning up the header dependencies. */
class ObjectRef {
friend class Manager;
friend class ObjectKey;
friend DupliCacheManager;
private:
/** Duplicated object that corresponds to the current object. */
DupliObject *const dupli_object_ = nullptr;
/** Object that created the dupli-list the current object is part of. */
Object *const dupli_parent_ = nullptr;
/** List of (render-compatible) duplis when rendering a ranges. */
const VectorList<DupliObject *> *duplis_ = nullptr;
/** Unique handle per object ref. */
ResourceHandleRange handle_ = {};
ResourceHandleRange sculpt_handle_ = {};
/* For ParticleSystems of the main object. */
uint sub_key_ = 0;
public:
Object *const object;
explicit ObjectRef(Object *ob,
Object *dupli_parent = nullptr,
DupliObject *dupli_object = nullptr);
explicit ObjectRef(Object &ob, Object *dupli_parent, const VectorList<DupliObject *> &duplis);
explicit ObjectRef(const ObjectRef &ob_ref, uint sub_key) : ObjectRef(ob_ref)
{
sub_key_ = sub_key;
};
/* Is the object coming from a Dupli system. */
bool is_dupli() const
{
return dupli_parent_ != nullptr;
}
bool is_active(const Object *active_object) const
{
return (dupli_parent_ ? dupli_parent_ : object) == active_object;
}
bool is_range() const
{
return duplis_ != nullptr;
}
int instances_count() const
{
if (duplis_) {
return duplis_->size();
}
return 1;
}
float4x4 object_to_world(int instance_index) const
{
if (is_range()) {
return float4x4((*duplis_)[instance_index]->mat);
}
else {
BLI_assert(instance_index == 0);
return object->object_to_world();
}
}
float4x4 object_to_world() const
{
BLI_assert(!is_range());
return object_to_world(0);
}
float random(int instance_index) const
{
if (instance_index != 0) {
BLI_assert(is_range());
return (*duplis_)[instance_index]->random_id * (1.0f / float(0xFFFFFFFF));
}
if (dupli_parent_ == nullptr) {
/* TODO(fclem): this is rather costly to do at draw time. Maybe we can
* put it in ob->runtime and make depsgraph ensure it is up to date. */
return BLI_hash_int_2d(BLI_hash_string(object->id.name + 2), 0) * (1.0f / float(0xFFFFFFFF));
}
return dupli_object_->random_id * (1.0f / float(0xFFFFFFFF));
}
bool find_rgba_attribute(const GPUUniformAttr &attr, int instance_index, float r_value[4]) const
{
if (instance_index != 0) {
BLI_assert(is_range());
if (attr.use_dupli) {
/* If requesting instance data, check the parent particle system and object. */
return BKE_object_dupli_find_rgba_attribute(
object, (*duplis_)[instance_index], dupli_parent_, attr.name, r_value);
}
}
/* If requesting instance data, check the parent particle system and object. */
if (attr.use_dupli) {
return BKE_object_dupli_find_rgba_attribute(
object, dupli_object_, dupli_parent_, attr.name, r_value);
}
return BKE_object_dupli_find_rgba_attribute(object, nullptr, nullptr, attr.name, r_value);
}
LightLinking *light_linking() const
{
return dupli_parent_ ? dupli_parent_->light_linking : object->light_linking;
}
uint recalc_flags(uint64_t last_update) const
{
/* TODO: There should also be a way to get the min last_update for all objects in the range. */
auto get_flags = [&](const bke::ObjectRuntime &runtime) {
uint flags = 0;
SET_FLAG_FROM_TEST(flags, runtime.last_update_transform > last_update, ID_RECALC_TRANSFORM);
SET_FLAG_FROM_TEST(flags, runtime.last_update_geometry > last_update, ID_RECALC_GEOMETRY);
SET_FLAG_FROM_TEST(flags, runtime.last_update_shading > last_update, ID_RECALC_SHADING);
return flags;
};
uint flags = get_flags(*object->runtime);
if (dupli_parent_) {
flags |= get_flags(*dupli_parent_->runtime);
}
return flags;
}
/* Particle data are stored in world space. If an object is instanced, the associated particle
* systems need to be offset appropriately. */
float4x4 particles_matrix() const
{
/* Objects with particles don't support instancing optimizations yet. */
BLI_assert(!is_range());
/* TODO: Pass particle systems as a separate ObRef? */
float4x4 dupli_mat = float4x4::identity();
if (dupli_parent_ && dupli_object_) {
if (dupli_object_->type & OB_DUPLICOLLECTION) {
Collection *collection = dupli_parent_->instance_collection;
if (collection != nullptr) {
dupli_mat[3] -= float4(float3(collection->instance_offset), 0.0f);
}
dupli_mat = dupli_parent_->object_to_world() * dupli_mat;
}
else {
dupli_mat = object->object_to_world() * math::invert(dupli_object_->ob->object_to_world());
}
}
return dupli_mat;
}
int preview_instance_index() const
{
if (dupli_object_) {
return dupli_object_->preview_instance_index;
}
return -1;
}
const bke::GeometrySet *preview_base_geometry() const
{
if (dupli_object_) {
return dupli_object_->preview_base_geometry;
}
return nullptr;
}
bool parent_is_in_edit_paint_mode(const Object *active_object,
eObjectMode ob_mode,
eContextObjectMode ctx_mode) const
{
/* TODO: Deduplicate code with Overlay engine.
* Move to BKE ? Or check if T72490 is still relevant. */
if (!dupli_parent_ || active_object != dupli_parent_) {
return false;
}
if (object->base_flag & BASE_FROM_DUPLI) {
/* TODO: Is this code reachable? */
return false;
}
if (dupli_parent_->runtime->sculpt_session &&
(dupli_parent_->runtime->sculpt_session->mode_type == OB_MODE_SCULPT))
{
return true;
}
if (ob_mode & (OB_MODE_ALL_PAINT | OB_MODE_ALL_PAINT_GPENCIL)) {
return true;
}
if (DRW_object_is_in_edit_mode(dupli_parent_)) {
/* Also check for context mode as the object mode is not 100% reliable. (see T72490) */
switch (dupli_parent_->type) {
case OB_MESH:
return ctx_mode == CTX_MODE_EDIT_MESH;
case OB_ARMATURE:
return ctx_mode == CTX_MODE_EDIT_ARMATURE;
case OB_CURVES_LEGACY:
return ctx_mode == CTX_MODE_EDIT_CURVE;
case OB_SURF:
return ctx_mode == CTX_MODE_EDIT_SURFACE;
case OB_LATTICE:
return ctx_mode == CTX_MODE_EDIT_LATTICE;
case OB_MBALL:
return ctx_mode == CTX_MODE_EDIT_METABALL;
case OB_FONT:
return ctx_mode == CTX_MODE_EDIT_TEXT;
case OB_CURVES:
return ctx_mode == CTX_MODE_EDIT_CURVES;
case OB_POINTCLOUD:
return ctx_mode == CTX_MODE_EDIT_POINTCLOUD;
case OB_GREASE_PENCIL:
return ctx_mode == CTX_MODE_EDIT_GREASE_PENCIL;
case OB_VOLUME:
/* No edit mode yet. */
return false;
default:
return false;
}
}
return false;
}
};
/* -------------------------------------------------------------------- */
/** \name ObjectKey
*
* Unique key to identify each object in a hash-map.
* Note that we get a unique key for each object component.
* \{ */
class ObjectKey {
/** Hash value of the key. */
uint64_t hash_value_ = 0;
/** Original Object or source object for duplis. */
Object *ob_ = nullptr;
/** Original Parent object for duplis. */
Object *parent_ = nullptr;
/** Dupli objects recursive unique identifier */
int id_[MAX_DUPLI_RECUR];
/** Used for particle system hair. */
int sub_key_ = 0;
public:
ObjectKey() = default;
ObjectKey(const ObjectRef &ob_ref, int instance_index)
{
ob_ = DEG_get_original(ob_ref.object);
hash_value_ = get_default_hash(ob_);
if (DupliObject *dupli = instance_index ? (*ob_ref.duplis_)[instance_index] :
ob_ref.dupli_object_)
{
parent_ = ob_ref.dupli_parent_;
hash_value_ = get_default_hash(hash_value_, get_default_hash(parent_));
for (int i : IndexRange(MAX_DUPLI_RECUR)) {
id_[i] = dupli->persistent_id[i];
if (id_[i] == std::numeric_limits<int>::max()) {
break;
}
hash_value_ = get_default_hash(hash_value_, get_default_hash(id_[i]));
}
}
if (ob_ref.sub_key_ != 0) {
sub_key_ = ob_ref.sub_key_;
hash_value_ = get_default_hash(hash_value_, get_default_hash(sub_key_));
}
}
ObjectKey(const ObjectRef &ob_ref) : ObjectKey(ob_ref, 0)
{
BLI_assert(!ob_ref.is_range());
}
/* Special handles that will have nullptr object.
* Used for inserting helper items inside the hash-maps without creating a dummy #Object. */
explicit ObjectKey(int key)
{
sub_key_ = key;
hash_value_ = get_default_hash(ob_);
hash_value_ = get_default_hash(hash_value_, get_default_hash(sub_key_));
}
uint64_t hash() const
{
return hash_value_;
}
bool operator==(const ObjectKey &k) const
{
if (hash_value_ != k.hash_value_) {
return false;
}
if (ob_ != k.ob_) {
return false;
}
if (parent_ != k.parent_) {
return false;
}
if (sub_key_ != k.sub_key_) {
return false;
}
if (parent_) {
for (int i : IndexRange(MAX_DUPLI_RECUR)) {
if (id_[i] != k.id_[i]) {
return false;
}
if (id_[i] == std::numeric_limits<int>::max()) {
break;
}
}
}
return true;
}
};
/** \} */
}; // namespace draw
} // namespace blender

View File

@@ -0,0 +1,421 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "DNA_userdef_types.h"
#include "BKE_paint.hh"
#include "BKE_paint_bvh.hh"
#include "BLI_math_base.h"
#include "GPU_compute.hh"
#include "draw_context_private.hh"
#include "draw_debug.hh"
#include "draw_defines.hh"
#include "draw_manager.hh"
#include "draw_pass.hh"
#include "draw_shader.hh"
namespace blender::draw {
std::atomic<uint32_t> Manager::global_sync_counter_ = 1;
Manager::~Manager()
{
for (gpu::Texture *texture : acquired_textures) {
/* Decrease refcount and free if 0. */
GPU_texture_free(texture);
}
}
void Manager::begin_sync(Object *object_active)
{
/* Add 2 to always have a non-null number even in case of overflow. */
sync_counter_ = (global_sync_counter_ += 2);
matrix_buf.swap();
bounds_buf.swap();
infos_buf.swap();
matrix_buf.current().trim_to_next_power_of_2(resource_len_);
bounds_buf.current().trim_to_next_power_of_2(resource_len_);
infos_buf.current().trim_to_next_power_of_2(resource_len_);
attributes_buf.trim_to_next_power_of_2(attribute_len_);
/* TODO: This means the reference is kept until further redraw or manager tear-down. Instead,
* they should be released after each draw loop. But for now, mimics old DRW behavior. */
for (gpu::Texture *texture : acquired_textures) {
/* Decrease refcount and free if 0. */
GPU_texture_free(texture);
}
acquired_textures.clear();
layer_attributes.clear();
/* For some reason, if this uninitialized data pattern was enabled (ie release asserts enabled),
* The viewport just gives up rendering objects on ARM64 devices. Possibly Mesa GLOn12-related. */
#if !defined(NDEBUG) && !defined(_M_ARM64)
/* Detect uninitialized data. */
memset(matrix_buf.current().data(),
0xF0,
matrix_buf.current().size() * sizeof(*matrix_buf.current().data()));
memset(bounds_buf.current().data(),
0xF0,
matrix_buf.current().size() * sizeof(*bounds_buf.current().data()));
memset(infos_buf.current().data(),
0xF0,
matrix_buf.current().size() * sizeof(*infos_buf.current().data()));
#endif
resource_len_ = 0;
attribute_len_ = 0;
/* TODO(fclem): Resize buffers if too big, but with an hysteresis threshold. */
this->object_active = object_active;
/* Init the 0 resource. */
resource_handle(float4x4::identity());
}
void Manager::sync_layer_attributes()
{
/* Sort the attribute IDs - the shaders use binary search. */
Vector<uint32_t> id_list;
id_list.reserve(layer_attributes.size());
for (uint32_t id : layer_attributes.keys()) {
id_list.append(id);
}
std::ranges::sort(id_list);
/* Look up the attributes. */
int count = 0, size = layer_attributes_buf.end() - layer_attributes_buf.begin();
for (uint32_t id : id_list) {
if (layer_attributes_buf[count].sync(
drw_get().scene, drw_get().view_layer, layer_attributes.lookup(id)))
{
/* Check if the buffer is full. */
if (++count == size) {
break;
}
}
}
layer_attributes_buf[0].buffer_length = count;
}
void Manager::end_sync()
{
GPU_debug_group_begin("Manager.end_sync");
sync_layer_attributes();
matrix_buf.current().push_update();
bounds_buf.current().push_update();
infos_buf.current().push_update();
attributes_buf.push_update();
layer_attributes_buf.push_update();
/* Useful for debugging the following resource finalize. But will trigger the drawing of the GPU
* debug draw/print buffers for every frame. Not nice for performance. */
// debug_bind();
DRW_submission_start();
/* Dispatch compute to finalize the resources on GPU. Save a bit of CPU time. */
uint thread_groups = divide_ceil_u(resource_len_, DRW_FINALIZE_GROUP_SIZE);
gpu::Shader *shader = DRW_shader_draw_resource_finalize_get();
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "resource_len", resource_len_);
GPU_storagebuf_bind(matrix_buf.current(), GPU_shader_get_ssbo_binding(shader, "matrix_buf"));
GPU_storagebuf_bind(bounds_buf.current(), GPU_shader_get_ssbo_binding(shader, "bounds_buf"));
GPU_storagebuf_bind(infos_buf.current(), GPU_shader_get_ssbo_binding(shader, "infos_buf"));
GPU_compute_dispatch(shader, thread_groups, 1, 1);
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
DRW_submission_end();
GPU_debug_group_end();
}
void Manager::debug_bind()
{
gpu::StorageBuf *gpu_buf = DebugDraw::get().gpu_draw_buf_get();
if (gpu_buf == nullptr) {
return;
}
GPU_storagebuf_bind(gpu_buf, DRW_DEBUG_DRAW_SLOT);
#ifndef DISABLE_DEBUG_SHADER_PRINT_BARRIER
/* Add a barrier to allow multiple shader writing to the same buffer. */
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
#endif
}
void Manager::resource_bind()
{
GPU_storagebuf_bind(matrix_buf.current(), DRW_OBJ_MAT_SLOT);
GPU_storagebuf_bind(infos_buf.current(), DRW_OBJ_INFOS_SLOT);
GPU_storagebuf_bind(attributes_buf, DRW_OBJ_ATTR_SLOT);
GPU_uniformbuf_bind(layer_attributes_buf, DRW_LAYER_ATTR_UBO_SLOT);
}
uint64_t Manager::fingerprint_get()
{
/* Covers new sync cycle, added resources and different #Manager. */
return sync_counter_ | (uint64_t(resource_len_) << 32);
}
ResourceHandleRange Manager::unique_handle_for_sculpt(const ObjectRef &ref)
{
if (ref.sculpt_handle_.is_valid()) {
return ref.sculpt_handle_;
}
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(*ref.object);
const Bounds<float3> bounds = bke::pbvh::bounds_get(pbvh);
const float3 center = math::midpoint(bounds.min, bounds.max);
const float3 half_extent = bounds.max - center;
/* WORKAROUND: Instead of breaking const correctness everywhere, we only break it for this. */
const_cast<ObjectRef &>(ref).sculpt_handle_ = resource_handle(
ref, nullptr, &center, &half_extent);
return ref.sculpt_handle_;
}
void Manager::compute_visibility(View &view)
{
bool freeze_culling = (USER_DEVELOPER_TOOL_TEST(&U, use_viewport_debug) && drw_get().v3d &&
(drw_get().v3d->debug_flag & V3D_DEBUG_FREEZE_CULLING) != 0);
BLI_assert_msg(view.manager_fingerprint_ != this->fingerprint_get(),
"Resources did not changed, no need to update");
view.manager_fingerprint_ = this->fingerprint_get();
view.bind();
view.compute_visibility(
bounds_buf.current(), infos_buf.current(), resource_len_, freeze_culling);
}
void Manager::ensure_visibility(View &view)
{
if (view.manager_fingerprint_ != this->fingerprint_get()) {
compute_visibility(view);
}
}
void Manager::generate_commands(PassMain &pass, View &view)
{
if (pass.is_empty()) {
return;
}
BLI_assert_msg((pass.manager_fingerprint_ != this->fingerprint_get()) ||
(pass.view_fingerprint_ != view.fingerprint_get()),
"Resources and view did not changed no need to update");
BLI_assert_msg((view.manager_fingerprint_ == this->fingerprint_get()) &&
(view.fingerprint_get() != 0),
"Resources or view changed, but compute_visibility was not called");
pass.manager_fingerprint_ = this->fingerprint_get();
pass.view_fingerprint_ = view.fingerprint_get();
pass.draw_commands_buf_.generate_commands(pass.headers_,
pass.commands_,
view.get_visibility_buffer(),
view.visibility_word_per_draw(),
view.view_len_,
pass.use_custom_ids);
}
void Manager::generate_commands(PassSortable &pass, View &view)
{
if (pass.is_empty()) {
return;
}
pass.sort();
generate_commands(static_cast<PassMain &>(pass), view);
}
void Manager::generate_commands(PassSimple &pass)
{
if (pass.is_empty()) {
return;
}
BLI_assert_msg(pass.manager_fingerprint_ != this->fingerprint_get(),
"Resources did not changed since last generate_command, no need to update");
pass.manager_fingerprint_ = this->fingerprint_get();
pass.draw_commands_buf_.generate_commands(pass.headers_, pass.commands_, pass.sub_passes_);
}
void Manager::warm_shader_specialization(PassMain &pass)
{
if (pass.is_empty()) {
return;
}
command::RecordingState state;
pass.warm_shader_specialization(state);
}
void Manager::warm_shader_specialization(PassSimple &pass)
{
if (pass.is_empty()) {
return;
}
command::RecordingState state;
pass.warm_shader_specialization(state);
}
void Manager::submit_only(PassMain &pass, View &view)
{
if (pass.is_empty()) {
return;
}
BLI_assert_msg(view.manager_fingerprint_ != 0, "compute_visibility was not called on this view");
BLI_assert_msg(view.manager_fingerprint_ == this->fingerprint_get(),
"Resources changed since last compute_visibility");
BLI_assert_msg(pass.manager_fingerprint_ != 0, "generate_command was not called on this pass");
BLI_assert_msg(pass.manager_fingerprint_ == this->fingerprint_get(),
"Resources changed since last generate_command");
/* The function generate_commands needs to be called for each view this pass is going to be
* submitted with. This is because the commands are stored inside the pass and not per view. */
BLI_assert_msg(pass.view_fingerprint_ == view.fingerprint_get(),
"View have changed since last generate_commands or "
"submitting with a different view");
debug_bind();
command::RecordingState state;
state.inverted_view = view.is_inverted();
view.bind();
pass.draw_commands_buf_.bind(state);
resource_bind();
pass.submit(state);
state.cleanup();
}
void Manager::submit(PassMain &pass, View &view)
{
if (pass.is_empty()) {
return;
}
if (view.manager_fingerprint_ != this->fingerprint_get()) {
compute_visibility(view);
}
if (pass.manager_fingerprint_ != this->fingerprint_get() ||
pass.view_fingerprint_ != view.fingerprint_get())
{
generate_commands(pass, view);
}
this->submit_only(pass, view);
}
void Manager::submit(PassSortable &pass, View &view)
{
if (pass.is_empty()) {
return;
}
pass.sort();
this->submit(static_cast<PassMain &>(pass), view);
}
void Manager::submit(PassSimple &pass, bool inverted_view)
{
if (pass.is_empty()) {
return;
}
if (!pass.has_generated_commands()) {
generate_commands(pass);
}
debug_bind();
command::RecordingState state;
state.inverted_view = inverted_view;
pass.draw_commands_buf_.bind(state);
resource_bind();
pass.submit(state);
state.cleanup();
}
void Manager::submit(PassSimple &pass, View &view)
{
if (pass.is_empty()) {
return;
}
debug_bind();
view.bind();
this->submit(pass, view.is_inverted());
}
Manager::SubmitDebugOutput Manager::submit_debug(PassSimple &pass, View &view)
{
submit(pass, view);
pass.draw_commands_buf_.resource_id_buf_.read();
Manager::SubmitDebugOutput output;
output.resource_id = {pass.draw_commands_buf_.resource_id_buf_.data(),
pass.draw_commands_buf_.resource_id_count_};
/* There is no visibility data for PassSimple. */
output.visibility = {static_cast<uint *>(view.get_visibility_buffer().data()), 0};
return output;
}
Manager::SubmitDebugOutput Manager::submit_debug(PassMain &pass, View &view)
{
submit(pass, view);
GPU_memory_barrier(GPU_BARRIER_BUFFER_UPDATE);
pass.draw_commands_buf_.resource_id_buf_.read();
view.get_visibility_buffer().read();
Manager::SubmitDebugOutput output;
output.resource_id = {pass.draw_commands_buf_.resource_id_buf_.data(),
pass.draw_commands_buf_.resource_id_count_};
output.visibility = {static_cast<uint *>(view.get_visibility_buffer().data()),
divide_ceil_u(resource_len_, 32)};
return output;
}
Manager::DataDebugOutput Manager::data_debug()
{
matrix_buf.current().read();
bounds_buf.current().read();
infos_buf.current().read();
Manager::DataDebugOutput output;
output.matrices = {matrix_buf.current().data(), resource_len_};
output.bounds = {bounds_buf.current().data(), resource_len_};
output.infos = {infos_buf.current().data(), resource_len_};
return output;
}
} // namespace blender::draw

View File

@@ -0,0 +1,509 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup draw
*
* `draw::Manager` is the interface between scene data and viewport engines.
*
* It holds per component data (`ObjectInfo`, `ObjectMatrices`, ...) indexed per `ResourceHandle`.
*
* \note It is currently work in progress and should replace the old global draw manager.
*/
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_sys_types.h"
#include "GPU_material.hh"
#include "draw_handle.hh"
#include "draw_resource.hh"
#include "draw_view.hh"
#include <atomic>
namespace blender {
namespace draw {
/* Forward declarations. */
namespace detail {
template<typename T> class Pass;
} // namespace detail
namespace command {
class DrawCommandBuf;
class DrawMultiBuf;
} // namespace command
using PassSimple = detail::Pass<command::DrawCommandBuf>;
using PassMain = detail::Pass<command::DrawMultiBuf>;
class PassSortable;
class Manager {
using ObjectMatricesBuf = StorageArrayBuffer<ObjectMatrices, 128>;
using ObjectBoundsBuf = StorageArrayBuffer<ObjectBounds, 128>;
using ObjectInfosBuf = StorageArrayBuffer<ObjectInfos, 128>;
using ObjectAttributeBuf = StorageArrayBuffer<ObjectAttribute, 128>;
using LayerAttributeBuf = UniformArrayBuffer<LayerAttribute, 512>;
/**
* TODO(@fclem): Remove once we get rid of old EEVEE code-base.
* `DRW_RESOURCE_CHUNK_LEN = 512`.
*/
using ObjectAttributeLegacyBuf = UniformArrayBuffer<float4, 8 * 512>;
public:
struct SubmitDebugOutput {
/** Indexed by resource id. */
Span<uint32_t> visibility;
/** Indexed by drawn instance. */
Span<uint32_t> resource_id;
};
struct DataDebugOutput {
/** Indexed by resource id. */
Span<ObjectMatrices> matrices;
/** Indexed by resource id. */
Span<ObjectBounds> bounds;
/** Indexed by resource id. */
Span<ObjectInfos> infos;
};
/**
* Buffers containing all object data. Referenced by resource index.
* Exposed as public members for shader access after sync.
*/
SwapChain<ObjectMatricesBuf, 2> matrix_buf;
SwapChain<ObjectBoundsBuf, 2> bounds_buf;
SwapChain<ObjectInfosBuf, 2> infos_buf;
/**
* Object Attributes are reference by indirection data inside ObjectInfos.
* This is because attribute list is arbitrary.
*/
ObjectAttributeBuf attributes_buf;
/**
* Table of all View Layer attributes required by shaders, used to populate the buffer below.
*/
Map<uint32_t, GPULayerAttr> layer_attributes;
/**
* Buffer of layer attribute values, indexed and sorted by the hash.
*/
LayerAttributeBuf layer_attributes_buf;
/**
* List of textures coming from Image data-blocks.
* They need to be reference-counted in order to avoid being freed in another thread.
*/
Vector<gpu::Texture *> acquired_textures;
private:
/** Number of sync done by managers. Used for fingerprint. */
static std::atomic<uint32_t> global_sync_counter_;
/* Local sync counter. Used for fingerprint. Must never be null. */
uint32_t sync_counter_ = 1;
/** Number of resource handle recorded. */
uint resource_len_ = 0;
/** Number of object attribute recorded. */
uint attribute_len_ = 0;
Object *object_active = nullptr;
public:
Manager() {};
~Manager();
/**
* Create a unique resource handle for the given object.
* Returns the existing handle if it exists.
*/
/* WORKAROUND: Instead of breaking const correctness everywhere, we only break it for this. */
ResourceHandleRange unique_handle(const ObjectRef &ref);
ResourceHandleRange unique_handle_for_sculpt(const ObjectRef &ref);
/**
* Create a new resource handle for the given object.
*/
ResourceHandleRange resource_handle(const ObjectRef &ref, float inflate_bounds = 0.0f);
/**
* Create a new resource handle for the given object, but optionally override model matrix and
* bounds.
*/
ResourceHandleRange resource_handle(const ObjectRef &ref,
const float4x4 *model_matrix,
const float3 *bounds_center,
const float3 *bounds_half_extent);
/**
* Get resource id for a loose matrix. The draw-calls for this resource handle won't be culled
* and there won't be any associated object info / bounds. Assumes correct handedness / winding.
*/
ResourceHandle resource_handle(const float4x4 &model_matrix);
/**
* Get resource id for a loose matrix with bounds. The draw-calls for this resource handle will
* be culled but there won't be any associated object info / bounds.
* Assumes correct handedness / winding.
*/
ResourceHandle resource_handle(const float4x4 &model_matrix,
const float3 &bounds_center,
const float3 &bounds_half_extent);
/**
* Get resource id for particle system. The draw-calls for this resource won't be culled. The
* associated object info will contain the info from its parent object.
*/
ResourceHandle resource_handle_for_psys(const ObjectRef &ref, const float4x4 &model_matrix);
/** Update the bounds of an already created handle. */
void update_handle_bounds(ResourceHandle handle,
const ObjectRef &ref,
float inflate_bounds = 0.0f);
/**
* Populate additional per resource data on demand.
* IMPORTANT: Should be called only **once** per object.
*/
void extract_object_attributes(ResourceHandleRange handle,
const ObjectRef &ref,
const GPUMaterial *material);
void extract_object_attributes(ResourceHandleRange handle,
const ObjectRef &ref,
Span<GPUMaterial *> materials);
/**
* Collect necessary View Layer attributes.
*/
void register_layer_attributes(GPUMaterial *material);
/**
* Compute <-> Graphic queue transition is quite slow on some backend. To avoid unnecessary
* switching, it is better to dispatch all visibility computation as soon as possible before any
* graphic work.
*
* Grouping the calls to `compute_visibility()` together is also beneficial for PSO switching
* overhead. Same thing applies to `generate_commands()`.
*
* IMPORTANT: Generated commands are stored inside #PassMain and overrides commands generated for
* a previous view.
*
* Before:
* \code{.cpp}
* manager.submit(pass1, view1);
* manager.submit(pass2, view1);
* manager.submit(pass1, view2);
* manager.submit(pass2, view2);
* \endcode
*
* After:
* \code{.cpp}
* manager.compute_visibility(view1);
* manager.compute_visibility(view2);
*
* manager.generate_commands(pass1, view1);
* manager.generate_commands(pass2, view1);
*
* manager.submit(pass1, view1);
* manager.submit(pass2, view1);
*
* manager.generate_commands(pass1, view2);
* manager.generate_commands(pass2, view2);
*
* manager.submit(pass1, view2);
* manager.submit(pass2, view2);
* \endcode
*/
/**
* Compute visibility of #ResourceHandle for the given #View.
* The commands needs to be regenerated for any change inside the #Manager or in the #View.
* Avoids just in time computation of visibility.
*/
void compute_visibility(View &view);
/**
* Same as compute_visibility but only do it if needed.
*/
void ensure_visibility(View &view);
/**
* Generate commands for #ResourceHandle for the given #View and #PassMain.
* The commands needs to be regenerated for any change inside the #Manager, the #PassMain or in
* the #View. Avoids just in time command generation.
*
* IMPORTANT: Generated commands are stored inside #PassMain and overrides commands previously
* generated for a previous view.
*/
void generate_commands(PassMain &pass, View &view);
void generate_commands(PassSortable &pass, View &view);
/**
* Generate commands on CPU. Doesn't have the GPU compute dispatch overhead.
*/
void generate_commands(PassSimple &pass);
/**
* Make sure the shader specialization constants are already compiled.
* This avoid stalling the real submission call because of specialization.
*/
void warm_shader_specialization(PassMain &pass);
void warm_shader_specialization(PassSimple &pass);
/**
* Submit a pass for drawing. All resource reference will be dereferenced and commands will be
* sent to GPU. Visibility and command generation **must** have already been done explicitly
* using `compute_visibility` and `generate_commands`.
*/
void submit_only(PassMain &pass, View &view);
/**
* Submit a pass for drawing. All resource reference will be dereferenced and commands will be
* sent to GPU. Visibility and command generation are run JIT if needed.
*/
void submit(PassSimple &pass, View &view);
void submit(PassMain &pass, View &view);
void submit(PassSortable &pass, View &view);
/**
* Variant without any view. Must not contain any shader using `draw_view` create info.
*/
void submit(PassSimple &pass, bool inverted_view = false);
/**
* Submit a pass for drawing but read back all data buffers for inspection.
*/
SubmitDebugOutput submit_debug(PassSimple &pass, View &view);
SubmitDebugOutput submit_debug(PassMain &pass, View &view);
/**
* Check data buffers of the draw manager. Only to be used after end_sync().
*/
DataDebugOutput data_debug();
/**
* Will acquire the texture using ref counting and release it after drawing. To be used for
* texture coming from blender Image.
*/
void acquire_texture(gpu::Texture *texture)
{
GPU_texture_ref(texture);
acquired_textures.append(texture);
}
/**
* Return the number of resource handles allocated.
*/
uint resource_handle_count() const
{
return resource_len_;
}
/** TODO(fclem): The following should become private at some point. */
void begin_sync(Object *object_active = nullptr);
void end_sync();
void debug_bind();
void resource_bind();
private:
void sync_layer_attributes();
/* Fingerprint of the manager in a certain state. Assured to not be 0.
* Not reliable enough for general update detection. Only to be used for debugging assertion. */
uint64_t fingerprint_get();
};
inline ResourceHandleRange Manager::unique_handle(const ObjectRef &ref)
{
if (!ref.handle_.is_valid()) {
/* WORKAROUND: Instead of breaking const correctness everywhere, we only break it for this. */
const_cast<ObjectRef &>(ref).handle_ = resource_handle(ref);
}
return ref.handle_;
}
inline ResourceHandleRange Manager::resource_handle(const ObjectRef &ref, float inflate_bounds)
{
bool is_active_object = ref.is_active(object_active);
bool is_active_edit_mode = object_active &&
(DRW_object_is_in_edit_mode(object_active) ||
ELEM(object_active->mode, OB_MODE_TEXTURE_PAINT, OB_MODE_SCULPT)) &&
ref.object->mode == object_active->mode;
if (ref.duplis_) {
uint start = resource_len_;
ObjectBounds proto_bounds;
proto_bounds.sync(*ref.object, inflate_bounds);
ObjectInfos proto_info;
proto_info.sync(ref, is_active_object, is_active_edit_mode);
for (const DupliObject *dupli : *ref.duplis_) {
matrix_buf.current().get_or_resize(resource_len_).sync(float4x4(dupli->mat));
bounds_buf.current().get_or_resize(resource_len_) = proto_bounds;
ObjectInfos &info = infos_buf.current().get_or_resize(resource_len_);
info = proto_info;
info.random = dupli->random_id * (1.0f / float(0xFFFFFFFF));
resource_len_++;
}
return ResourceHandleRange(ResourceHandle(start, (ref.object->transflag & OB_NEG_SCALE) != 0),
resource_len_ - start);
}
else {
matrix_buf.current().get_or_resize(resource_len_).sync(*ref.object);
bounds_buf.current().get_or_resize(resource_len_).sync(*ref.object, inflate_bounds);
infos_buf.current()
.get_or_resize(resource_len_)
.sync(ref, is_active_object, is_active_edit_mode);
return ResourceHandle(resource_len_++, (ref.object->transflag & OB_NEG_SCALE) != 0);
}
}
inline ResourceHandleRange Manager::resource_handle(const ObjectRef &ref,
const float4x4 *model_matrix,
const float3 *bounds_center,
const float3 *bounds_half_extent)
{
BLI_assert(!ref.duplis_);
bool is_active_object = ref.is_active(object_active);
bool is_active_edit_mode = object_active &&
(DRW_object_is_in_edit_mode(object_active) ||
ELEM(object_active->mode, OB_MODE_TEXTURE_PAINT, OB_MODE_SCULPT)) &&
ref.object->mode == object_active->mode;
if (model_matrix) {
matrix_buf.current().get_or_resize(resource_len_).sync(*model_matrix);
}
else {
matrix_buf.current().get_or_resize(resource_len_).sync(*ref.object);
}
if (bounds_center && bounds_half_extent) {
bounds_buf.current().get_or_resize(resource_len_).sync(*bounds_center, *bounds_half_extent);
}
else {
bounds_buf.current().get_or_resize(resource_len_).sync(*ref.object);
}
infos_buf.current()
.get_or_resize(resource_len_)
.sync(ref, is_active_object, is_active_edit_mode);
return ResourceHandle(resource_len_++, (ref.object->transflag & OB_NEG_SCALE) != 0);
}
inline ResourceHandle Manager::resource_handle(const float4x4 &model_matrix)
{
matrix_buf.current().get_or_resize(resource_len_).sync(model_matrix);
bounds_buf.current().get_or_resize(resource_len_).sync();
infos_buf.current().get_or_resize(resource_len_).sync();
return ResourceHandle(resource_len_++, false);
}
inline ResourceHandle Manager::resource_handle(const float4x4 &model_matrix,
const float3 &bounds_center,
const float3 &bounds_half_extent)
{
matrix_buf.current().get_or_resize(resource_len_).sync(model_matrix);
bounds_buf.current().get_or_resize(resource_len_).sync(bounds_center, bounds_half_extent);
infos_buf.current().get_or_resize(resource_len_).sync();
return ResourceHandle(resource_len_++, false);
}
inline ResourceHandle Manager::resource_handle_for_psys(const ObjectRef &ref,
const float4x4 &model_matrix)
{
BLI_assert(!ref.duplis_);
bool is_active_object = ref.is_active(object_active);
bool is_active_edit_mode = object_active &&
(DRW_object_is_in_edit_mode(object_active) ||
ELEM(object_active->mode, OB_MODE_TEXTURE_PAINT, OB_MODE_SCULPT)) &&
ref.object->mode == object_active->mode;
matrix_buf.current().get_or_resize(resource_len_).sync(model_matrix);
bounds_buf.current().get_or_resize(resource_len_).sync();
infos_buf.current()
.get_or_resize(resource_len_)
.sync(ref, is_active_object, is_active_edit_mode);
return ResourceHandle(resource_len_++, (ref.object->transflag & OB_NEG_SCALE) != 0);
}
inline void Manager::update_handle_bounds(ResourceHandle handle,
const ObjectRef &ref,
float inflate_bounds)
{
bounds_buf.current()[handle.index()].sync(*ref.object, inflate_bounds);
}
inline void Manager::extract_object_attributes(ResourceHandleRange handle,
const ObjectRef &ref,
const GPUMaterial *material)
{
const GPUUniformAttrList *attr_list = GPU_material_uniform_attributes(material);
if (attr_list == nullptr || attr_list->count == 0) {
return;
}
int instance_index = 0;
for (ResourceID resource_id : handle.id_range()) {
ObjectInfos &infos = infos_buf.current().get_or_resize(resource_id.index());
infos.object_attrs_offset = attribute_len_;
for (const GPUUniformAttr &attr : attr_list->list) {
if (attributes_buf.get_or_resize(attribute_len_).sync(ref, *&attr, instance_index)) {
infos.object_attrs_len++;
attribute_len_++;
}
}
instance_index++;
}
}
inline void Manager::extract_object_attributes(ResourceHandleRange handle,
const ObjectRef &ref,
Span<GPUMaterial *> materials)
{
Map<uint32_t, const GPUUniformAttr *, 4> attributes;
for (const GPUMaterial *mat : materials) {
const GPUUniformAttrList *attr_list = GPU_material_uniform_attributes(mat);
if (attr_list == nullptr) {
continue;
}
for (const GPUUniformAttr &attr : attr_list->list) {
attributes.add(attr.hash_code, &attr);
}
}
int instance_index = 0;
for (ResourceID resource_id : handle.id_range()) {
ObjectInfos &infos = infos_buf.current().get_or_resize(resource_id.index());
infos.object_attrs_offset = attribute_len_;
for (const GPUUniformAttr *attr : attributes.values()) {
if (attributes_buf.get_or_resize(attribute_len_).sync(ref, *attr, instance_index)) {
infos.object_attrs_len++;
attribute_len_++;
}
}
instance_index++;
}
}
inline void Manager::register_layer_attributes(GPUMaterial *material)
{
const ListBaseT<GPULayerAttr> *attr_list = GPU_material_layer_attributes(material);
if (attr_list != nullptr) {
for (const GPULayerAttr &attr : *attr_list) {
/** Since layer attributes are global to the whole render pass,
* this only collects a table of their names. */
layer_attributes.add(attr.hash_code, *&attr);
}
}
}
} // namespace draw
/* TODO(@fclem): This is for testing. The manager should be passed to the engine through the
* callbacks. */
draw::Manager *DRW_manager_get();
} // namespace blender

View File

@@ -0,0 +1,684 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_color.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_memiter.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BKE_editmesh.hh"
#include "BKE_editmesh_cache.hh"
#include "BKE_global.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_types.hh"
#include "BKE_mesh_wrapper.hh"
#include "BKE_object.hh"
#include "BKE_unit.hh"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "DNA_view3d_types.h"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "ED_view3d.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BLF_api.hh"
#include "WM_api.hh"
#include "draw_manager_text.hh"
#include "intern/bmesh_polygon.hh"
namespace blender {
struct ViewCachedString {
float vec[3];
union {
uchar ub[4];
int pack;
} col;
short sco[2];
short xoffs, yoffs;
short flag;
int str_len;
bool shadow;
bool align_center;
/* str is allocated past the end */
char str[0];
};
struct DRWTextStore {
BLI_memiter *cache_strings;
};
DRWTextStore *DRW_text_cache_create()
{
DRWTextStore *dt = MEM_new_zeroed<DRWTextStore>(__func__);
dt->cache_strings = BLI_memiter_create(1 << 14); /* 16kb */
return dt;
}
void DRW_text_cache_destroy(DRWTextStore *dt)
{
if (dt == nullptr) {
return;
}
BLI_memiter_destroy(dt->cache_strings);
MEM_delete(dt);
}
void DRW_text_cache_add(DRWTextStore *dt,
const float co[3],
const char *str,
const int str_len,
short xoffs,
short yoffs,
short flag,
const uchar col[4],
const bool shadow,
const bool align_center)
{
int alloc_len;
ViewCachedString *vos;
if (flag & DRW_TEXT_CACHE_STRING_PTR) {
BLI_assert(str_len == strlen(str));
alloc_len = sizeof(void *);
}
else {
alloc_len = str_len + 1;
}
vos = static_cast<ViewCachedString *>(
BLI_memiter_alloc(dt->cache_strings, sizeof(ViewCachedString) + alloc_len));
copy_v3_v3(vos->vec, co);
copy_v4_v4_uchar(vos->col.ub, col);
vos->xoffs = xoffs;
vos->yoffs = yoffs;
vos->flag = flag;
vos->str_len = str_len;
vos->shadow = shadow;
vos->align_center = align_center;
/* allocate past the end */
if (flag & DRW_TEXT_CACHE_STRING_PTR) {
memcpy(vos->str, &str, alloc_len);
}
else {
memcpy(vos->str, str, alloc_len);
}
}
static void drw_text_cache_draw_ex(const DRWTextStore *dt, const ARegion *region)
{
ViewCachedString *vos;
BLI_memiter_handle it;
int col_pack_prev = 0;
float original_proj[4][4];
GPU_matrix_projection_get(original_proj);
wmOrtho2_region_pixelspace(region);
GPU_matrix_push();
GPU_matrix_identity_set();
BLF_default_size(ui::style_get()->widget.points);
const int font_id = BLF_set_default();
float outline_dark_color[4] = {0, 0, 0, 0.8f};
float outline_light_color[4] = {1, 1, 1, 0.8f};
bool outline_is_dark = true;
BLI_memiter_iter_init(dt->cache_strings, &it);
while ((vos = static_cast<ViewCachedString *>(BLI_memiter_iter_step(&it)))) {
if (vos->sco[0] != IS_CLIPPED) {
if (col_pack_prev != vos->col.pack) {
BLF_color4ubv(font_id, vos->col.ub);
const uchar lightness = srgb_to_grayscale_byte(vos->col.ub);
outline_is_dark = lightness > 96;
col_pack_prev = vos->col.pack;
}
if (vos->align_center) {
/* Measure the size of the string, then offset to align to the vertex. */
float width, height;
BLF_width_and_height(font_id,
(vos->flag & DRW_TEXT_CACHE_STRING_PTR) ?
*(reinterpret_cast<const char **>(vos->str)) :
vos->str,
vos->str_len,
&width,
&height);
vos->xoffs -= short(width / 2.0f);
vos->yoffs -= short(height / 2.0f);
}
const int font_id = BLF_default();
if (vos->shadow) {
BLF_enable(font_id, BLF_SHADOW);
BLF_shadow(font_id,
FontShadowType::Outline,
outline_is_dark ? outline_dark_color : outline_light_color);
BLF_shadow_offset(font_id, 0, 0);
}
else {
BLF_disable(font_id, BLF_SHADOW);
}
BLF_draw_default(float(vos->sco[0] + vos->xoffs),
float(vos->sco[1] + vos->yoffs),
2.0f,
(vos->flag & DRW_TEXT_CACHE_STRING_PTR) ?
*(reinterpret_cast<const char **>(vos->str)) :
vos->str,
vos->str_len);
}
}
GPU_matrix_pop();
GPU_matrix_projection_set(original_proj);
}
void DRW_text_cache_draw(const DRWTextStore *dt, const ARegion *region, const View3D *v3d)
{
ViewCachedString *vos;
if (v3d) {
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
int tot = 0;
/* project first and test */
BLI_memiter_handle it;
BLI_memiter_iter_init(dt->cache_strings, &it);
while ((vos = static_cast<ViewCachedString *>(BLI_memiter_iter_step(&it)))) {
if (ED_view3d_project_short_ex(
region,
(vos->flag & DRW_TEXT_CACHE_GLOBALSPACE) ? rv3d->persmat : rv3d->persmatob,
(vos->flag & DRW_TEXT_CACHE_LOCALCLIP) != 0,
vos->vec,
vos->sco,
V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_WIN | V3D_PROJ_TEST_CLIP_NEAR) ==
V3D_PROJ_RET_OK)
{
tot++;
}
else {
vos->sco[0] = IS_CLIPPED;
}
}
if (tot) {
/* Disable clipping for text */
const bool rv3d_clipping_enabled = RV3D_CLIPPING_ENABLED(v3d, rv3d);
if (rv3d_clipping_enabled) {
GPU_clip_distances(0);
}
drw_text_cache_draw_ex(dt, region);
if (rv3d_clipping_enabled) {
GPU_clip_distances(6);
}
}
}
else {
/* project first */
BLI_memiter_handle it;
BLI_memiter_iter_init(dt->cache_strings, &it);
const View2D *v2d = &region->v2d;
float viewmat[4][4];
rctf region_space = {0.0f, float(region->winx), 0.0f, float(region->winy)};
BLI_rctf_transform_calc_m4_pivot_min(&v2d->cur, &region_space, viewmat);
while ((vos = static_cast<ViewCachedString *>(BLI_memiter_iter_step(&it)))) {
float p[3];
copy_v3_v3(p, vos->vec);
mul_m4_v3(viewmat, p);
vos->sco[0] = p[0];
vos->sco[1] = p[1];
}
drw_text_cache_draw_ex(dt, region);
}
}
void DRW_text_edit_mesh_measure_stats(const ARegion *region,
const View3D *v3d,
const Object *ob,
const UnitSettings &unit,
DRWTextStore *dt)
{
/* Do not use ASCII when using non-default unit system, some unit chars are UTF8
* (micro, square, etc.). See #36090. */
const short txt_flag = DRW_TEXT_CACHE_GLOBALSPACE;
const Mesh *mesh = BKE_object_get_editmesh_eval_cage(ob);
if (!mesh) {
return;
}
const BMEditMesh *em = mesh->runtime->edit_mesh.get();
if (!BKE_editmesh_eval_orig_map_available(*mesh, BKE_object_get_pre_modified_mesh(ob))) {
return;
}
char numstr[32]; /* Stores the measurement display text here */
const char *conv_float; /* Use a float conversion matching the grid size */
uchar4 col = {0, 0, 0, 255}; /* color of the text to draw */
const float grid = unit.system ? unit.scale_length : v3d->grid;
const bool do_global = (v3d->flag & V3D_GLOBAL_STATS) != 0;
const bool do_moving = (G.moving & G_TRANSFORM_EDIT) != 0;
float4x4 clip_planes;
/* allow for displaying shape keys and deform mods */
BMIter iter;
const Span<float3> vert_positions = BKE_mesh_wrapper_vert_coords(mesh);
const bool use_coords = !vert_positions.is_empty();
/* when 2 or more edge-info options are enabled, space apart */
short edge_tex_count = 0;
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_EDGE_LEN) {
edge_tex_count += 1;
}
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_EDGE_ANG) {
edge_tex_count += 1;
}
if ((v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_INDICES) && (em->selectmode & SCE_SELECT_EDGE)) {
edge_tex_count += 1;
}
const short edge_tex_sep = short((edge_tex_count - 1) * 5.0f * UI_SCALE_FAC);
/* Make the precision of the display value proportionate to the grid-size. */
if (grid <= 0.01f) {
conv_float = "%.6g";
}
else if (grid <= 0.1f) {
conv_float = "%.5g";
}
else if (grid <= 1.0f) {
conv_float = "%.4g";
}
else if (grid <= 10.0f) {
conv_float = "%.3g";
}
else {
conv_float = "%.2g";
}
if (v3d->overlay.edit_flag &
(V3D_OVERLAY_EDIT_EDGE_LEN | V3D_OVERLAY_EDIT_EDGE_ANG | V3D_OVERLAY_EDIT_INDICES))
{
BoundBox bb;
const rcti rect = {0, region->winx, 0, region->winy};
ED_view3d_clipping_calc(&bb, clip_planes.ptr(), region, ob, &rect);
}
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_EDGE_LEN) {
BMEdge *eed;
ui::theme::get_color_3ubv(TH_DRAWEXTRA_EDGELEN, col);
if (use_coords) {
BM_mesh_elem_index_ensure(em->bm, BM_VERT);
}
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
/* draw selected edges, or edges next to selected verts while dragging */
if (BM_elem_flag_test(eed, BM_ELEM_SELECT) ||
(do_moving && (BM_elem_flag_test(eed->v1, BM_ELEM_SELECT) ||
BM_elem_flag_test(eed->v2, BM_ELEM_SELECT))))
{
float3 v1, v2;
float3 v1_clip, v2_clip;
if (use_coords) {
v1 = vert_positions[BM_elem_index_get(eed->v1)];
v2 = vert_positions[BM_elem_index_get(eed->v2)];
}
else {
v1 = eed->v1->co;
v2 = eed->v2->co;
}
if (clip_segment_v3_plane_n(v1, v2, clip_planes.ptr(), 4, v1_clip, v2_clip)) {
const float3 co = math::transform_point(ob->object_to_world(),
0.5 * (v1_clip + v2_clip));
if (do_global) {
v1 = ob->object_to_world().view<3, 3>() * v1;
v2 = ob->object_to_world().view<3, 3>() * v2;
}
const size_t numstr_len = unit.system ?
BKE_unit_value_as_string_scaled(numstr,
sizeof(numstr),
len_v3v3(v1, v2),
3,
B_UNIT_LENGTH,
unit,
false,
true) :
SNPRINTF_RLEN(numstr, conv_float, len_v3v3(v1, v2));
DRW_text_cache_add(dt, co, numstr, numstr_len, 0, edge_tex_sep, txt_flag, col);
}
}
}
}
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_EDGE_ANG) {
const bool is_rad = (unit.system_rotation == USER_UNIT_ROT_RADIANS);
BMEdge *eed;
ui::theme::get_color_3ubv(TH_DRAWEXTRA_EDGEANG, col);
Span<float3> face_normals;
if (use_coords) {
BM_mesh_elem_index_ensure(em->bm, BM_VERT | BM_FACE);
/* TODO: This is not const correct for wrapper meshes, but it should be okay because
* every evaluated object gets its own evaluated cage mesh (they are not shared). */
face_normals = BKE_mesh_wrapper_face_normals(const_cast<Mesh *>(mesh));
}
BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) {
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(eed, &l_a, &l_b)) {
/* Draw selected edges, or edges next to selected verts while dragging. */
if (BM_elem_flag_test(eed, BM_ELEM_SELECT) ||
(do_moving && (BM_elem_flag_test(eed->v1, BM_ELEM_SELECT) ||
BM_elem_flag_test(eed->v2, BM_ELEM_SELECT) ||
/* Special case, this is useful to show when verts connected
* to this edge via a face are being transformed. */
BM_elem_flag_test(l_a->next->next->v, BM_ELEM_SELECT) ||
BM_elem_flag_test(l_a->prev->v, BM_ELEM_SELECT) ||
BM_elem_flag_test(l_b->next->next->v, BM_ELEM_SELECT) ||
BM_elem_flag_test(l_b->prev->v, BM_ELEM_SELECT))))
{
float3 v1, v2;
float3 v1_clip, v2_clip;
if (use_coords) {
v1 = vert_positions[BM_elem_index_get(eed->v1)];
v2 = vert_positions[BM_elem_index_get(eed->v2)];
}
else {
v1 = eed->v1->co;
v2 = eed->v2->co;
}
if (clip_segment_v3_plane_n(v1, v2, clip_planes.ptr(), 4, v1_clip, v2_clip)) {
float3 no_a, no_b;
const float3 co = math::transform_point(ob->object_to_world(),
0.5 * (v1_clip + v2_clip));
if (use_coords) {
no_a = face_normals[BM_elem_index_get(l_a->f)];
no_b = face_normals[BM_elem_index_get(l_b->f)];
}
else {
no_a = l_a->f->no;
no_b = l_b->f->no;
}
if (do_global) {
no_a = math::normalize(ob->world_to_object().view<3, 3>() * no_a);
no_b = math::normalize(ob->world_to_object().view<3, 3>() * no_b);
}
const float angle = angle_normalized_v3v3(no_a, no_b);
const size_t numstr_len = SNPRINTF_RLEN(numstr,
"%.3f%s",
(is_rad) ? angle : RAD2DEGF(angle),
(is_rad) ? "r" : BLI_STR_UTF8_DEGREE_SIGN);
DRW_text_cache_add(dt, co, numstr, numstr_len, 0, -edge_tex_sep, txt_flag, col);
}
}
}
}
}
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_FACE_AREA) {
/* would be nice to use BM_face_calc_area, but that is for 2d faces
* so instead add up tessellation triangle areas */
ui::theme::get_color_3ubv(TH_DRAWEXTRA_FACEAREA, col);
int i;
BMFace *f = nullptr;
/* Alternative to using `poly_to_tri_count(i, BM_elem_index_get(f->l_first))`
* without having to add an extra loop. */
int tri_index = 0;
BM_ITER_MESH_INDEX (f, &iter, em->bm, BM_FACES_OF_MESH, i) {
const int f_corner_tris_len = f->len - 2;
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
int n = 0;
float area = 0; /* area of the face */
float3 vmid(0.0f);
const std::array<BMLoop *, 3> *ltri_array = &em->looptris[tri_index];
for (int j = 0; j < f_corner_tris_len; j++) {
float3 v1, v2, v3;
if (use_coords) {
v1 = vert_positions[BM_elem_index_get(ltri_array[j][0]->v)];
v2 = vert_positions[BM_elem_index_get(ltri_array[j][1]->v)];
v3 = vert_positions[BM_elem_index_get(ltri_array[j][2]->v)];
}
else {
v1 = ltri_array[j][0]->v->co;
v2 = ltri_array[j][1]->v->co;
v3 = ltri_array[j][2]->v->co;
}
vmid += v1;
vmid += v2;
vmid += v3;
n += 3;
if (do_global) {
v1 = ob->object_to_world().view<3, 3>() * v1;
v2 = ob->object_to_world().view<3, 3>() * v2;
v3 = ob->object_to_world().view<3, 3>() * v3;
}
area += area_tri_v3(v1, v2, v3);
}
vmid *= 1.0f / float(n);
vmid = math::transform_point(ob->object_to_world(), vmid);
const size_t numstr_len =
unit.system ? BKE_unit_value_as_string_scaled(
numstr, sizeof(numstr), area, 3, B_UNIT_AREA, unit, false, true) :
SNPRINTF_RLEN(numstr, conv_float, area);
DRW_text_cache_add(dt, vmid, numstr, numstr_len, 0, 0, txt_flag, col);
}
tri_index += f_corner_tris_len;
}
}
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_FACE_ANG) {
BMFace *efa;
const bool is_rad = (unit.system_rotation == USER_UNIT_ROT_RADIANS);
ui::theme::get_color_3ubv(TH_DRAWEXTRA_FACEANG, col);
if (use_coords) {
BM_mesh_elem_index_ensure(em->bm, BM_VERT);
}
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
const bool is_face_sel = BM_elem_flag_test_bool(efa, BM_ELEM_SELECT);
if (is_face_sel || do_moving) {
BMIter liter;
BMLoop *loop;
bool is_first = true;
BM_ITER_ELEM (loop, &liter, efa, BM_LOOPS_OF_FACE) {
if (is_face_sel || (do_moving && (BM_elem_flag_test(loop->v, BM_ELEM_SELECT) ||
BM_elem_flag_test(loop->prev->v, BM_ELEM_SELECT) ||
BM_elem_flag_test(loop->next->v, BM_ELEM_SELECT))))
{
float3 v1, v2, v3;
float3 vmid;
/* lazy init center calc */
if (is_first) {
if (use_coords) {
BM_face_calc_center_bounds_vcos(em->bm, efa, vmid, vert_positions);
}
else {
BM_face_calc_center_bounds(efa, vmid);
}
is_first = false;
}
if (use_coords) {
v1 = vert_positions[BM_elem_index_get(loop->prev->v)];
v2 = vert_positions[BM_elem_index_get(loop->v)];
v3 = vert_positions[BM_elem_index_get(loop->next->v)];
}
else {
v1 = loop->prev->v->co;
v2 = loop->v->co;
v3 = loop->next->v->co;
}
const float3 v2_local = v2;
if (do_global) {
v1 = ob->object_to_world().view<3, 3>() * v1;
v2 = ob->object_to_world().view<3, 3>() * v2;
v3 = ob->object_to_world().view<3, 3>() * v3;
}
const float angle = angle_v3v3v3(v1, v2, v3);
const size_t numstr_len = SNPRINTF_RLEN(numstr,
"%.3f%s",
(is_rad) ? angle : RAD2DEGF(angle),
(is_rad) ? "r" : BLI_STR_UTF8_DEGREE_SIGN);
const float3 co = math::transform_point(ob->object_to_world(),
math::interpolate(vmid, v2_local, 0.8f));
DRW_text_cache_add(dt, co, numstr, numstr_len, 0, 0, txt_flag, col);
}
}
}
}
}
/* This option is for mesh ops and addons debugging; only available in UI if Blender starts
* with
* --debug */
if (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_INDICES) {
int i;
ui::theme::get_color_4ubv(TH_TEXT_HI, col);
if (em->selectmode & SCE_SELECT_VERTEX) {
BMVert *v;
if (use_coords) {
BM_mesh_elem_index_ensure(em->bm, BM_VERT);
}
BM_ITER_MESH_INDEX (v, &iter, em->bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT)) {
const float3 co = math::transform_point(
ob->object_to_world(), use_coords ? vert_positions[BM_elem_index_get(v)] : v->co);
const size_t numstr_len = SNPRINTF_RLEN(numstr, "%d", i);
DRW_text_cache_add(dt, co, numstr, numstr_len, 0, 0, txt_flag, col, true, false);
}
}
}
if (em->selectmode & SCE_SELECT_EDGE) {
BMEdge *eed;
const bool use_edge_tex_sep = (edge_tex_count == 2);
const bool use_edge_tex_len = (v3d->overlay.edit_flag & V3D_OVERLAY_EDIT_EDGE_LEN);
BM_ITER_MESH_INDEX (eed, &iter, em->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
float3 v1, v2;
float3 v1_clip, v2_clip;
if (use_coords) {
v1 = vert_positions[BM_elem_index_get(eed->v1)];
v2 = vert_positions[BM_elem_index_get(eed->v2)];
}
else {
v1 = eed->v1->co;
v2 = eed->v2->co;
}
if (clip_segment_v3_plane_n(v1, v2, clip_planes.ptr(), 4, v1_clip, v2_clip)) {
const float3 co = math::transform_point(ob->object_to_world(),
0.5 * (v1_clip + v2_clip));
const size_t numstr_len = SNPRINTF_RLEN(numstr, "%d", i);
DRW_text_cache_add(
dt,
co,
numstr,
numstr_len,
0,
(use_edge_tex_sep) ? (use_edge_tex_len) ? -edge_tex_sep : edge_tex_sep : 0,
txt_flag,
col,
true,
false);
}
}
}
}
if (em->selectmode & SCE_SELECT_FACE) {
BMFace *f;
if (use_coords) {
BM_mesh_elem_index_ensure(em->bm, BM_VERT);
}
BM_ITER_MESH_INDEX (f, &iter, em->bm, BM_FACES_OF_MESH, i) {
if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
float3 co;
if (use_coords) {
BM_face_calc_center_median_vcos(em->bm, f, co, vert_positions);
}
else {
BM_face_calc_center_median(f, co);
}
co = math::transform_point(ob->object_to_world(), co);
const size_t numstr_len = SNPRINTF_RLEN(numstr, "%d", i);
DRW_text_cache_add(dt, co, numstr, numstr_len, 0, 0, txt_flag, col, true, false);
}
}
}
}
}
} // namespace blender

View File

@@ -0,0 +1,54 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_sys_types.h"
namespace blender {
#pragma once
struct ARegion;
struct DRWTextStore;
struct Object;
struct UnitSettings;
struct View3D;
DRWTextStore *DRW_text_cache_create();
void DRW_text_cache_destroy(DRWTextStore *dt);
/* `draw_manager.cc` */
DRWTextStore *DRW_text_cache_ensure();
void DRW_text_cache_add(DRWTextStore *dt,
const float co[3],
const char *str,
int str_len,
short xoffs,
short yoffs,
short flag,
const uchar col[4],
const bool shadow = false,
const bool align_center = false);
void DRW_text_cache_draw(const DRWTextStore *dt, const ARegion *region, const View3D *v3d);
void DRW_text_edit_mesh_measure_stats(const ARegion *region,
const View3D *v3d,
const Object *ob,
const UnitSettings &unit,
DRWTextStore *dt = DRW_text_cache_ensure());
enum {
// DRW_UNUSED_1 = (1 << 0), /* dirty */
DRW_TEXT_CACHE_GLOBALSPACE = (1 << 1),
DRW_TEXT_CACHE_LOCALCLIP = (1 << 2),
/* reference the string by pointer */
DRW_TEXT_CACHE_STRING_PTR = (1 << 3),
};
} // 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,125 @@
/* SPDX-FileCopyrightText: 2017 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "DNA_pointcloud_types.h"
#include "GPU_batch.hh"
#include "GPU_material.hh"
#include "GPU_shader.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
#include "DRW_render.hh"
#include "draw_cache_impl.hh"
#include "draw_common.hh"
#include "draw_common_c.hh"
#include "draw_context_private.hh"
#include "draw_pointcloud_private.hh"
/* For drw_curves_get_attribute_sampler_name. */
#include "draw_curves_private.hh"
namespace blender::draw {
struct PointCloudModule {
gpu::VertBuf *dummy_vbo = create_dummy_vbo();
~PointCloudModule()
{
GPU_VERTBUF_DISCARD_SAFE(dummy_vbo);
}
private:
gpu::VertBuf *create_dummy_vbo()
{
GPUVertFormat format = {0};
uint dummy_id = GPU_vertformat_attr_add(
&format, "dummy", gpu::VertAttrType::SFLOAT_32_32_32_32);
gpu::VertBuf *vbo = GPU_vertbuf_create_with_format_ex(
format, GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY);
const float vert[4] = {0.0f, 0.0f, 0.0f, 0.0f};
GPU_vertbuf_data_alloc(*vbo, 1);
GPU_vertbuf_attr_fill(vbo, dummy_id, vert);
return vbo;
}
};
void DRW_pointcloud_init(DRWData *drw_data)
{
if (drw_data == nullptr) {
drw_data = drw_get().data;
}
if (drw_data->pointcloud_module == nullptr) {
drw_data->pointcloud_module = MEM_new<PointCloudModule>("PointCloudModule");
}
}
void DRW_pointcloud_module_free(PointCloudModule *pointcloud_module)
{
MEM_delete(pointcloud_module);
}
template<typename PassT>
gpu::Batch *pointcloud_sub_pass_setup_implementation(PassT &sub_ps,
Object *object,
GPUMaterial *gpu_material)
{
BLI_assert(object->type == OB_POINTCLOUD);
PointCloud &pointcloud = DRW_object_get_data_for_drawing<PointCloud>(*object);
/* An empty point cloud should never result in a draw-call. However, the buffer binding commands
* will still be executed. In this case, in order to avoid assertion, we bind dummy VBOs. */
bool is_empty = pointcloud.totpoint == 0;
PointCloudModule &module = *drw_get().data->pointcloud_module;
/* Ensure we have no unbound resources.
* Required for Vulkan.
* Fixes issues with certain GL drivers not drawing anything. */
sub_ps.bind_texture("u", module.dummy_vbo);
sub_ps.bind_texture("au", module.dummy_vbo);
sub_ps.bind_texture("a", module.dummy_vbo);
sub_ps.bind_texture("c", module.dummy_vbo);
sub_ps.bind_texture("ac", module.dummy_vbo);
gpu::VertBuf *pos_rad_buf = pointcloud_position_and_radius_get(&pointcloud);
sub_ps.bind_texture("ptcloud_pos_rad_tx", is_empty ? module.dummy_vbo : pos_rad_buf);
if (gpu_material != nullptr) {
ListBaseT<GPUMaterialAttribute> gpu_attrs = GPU_material_attributes(gpu_material);
for (GPUMaterialAttribute &gpu_attr : gpu_attrs) {
char sampler_name[32];
/** NOTE: Reusing curve attribute function. */
drw_curves_get_attribute_sampler_name(gpu_attr.name, sampler_name);
gpu::VertBuf **attribute_buf = DRW_pointcloud_evaluated_attribute(&pointcloud,
gpu_attr.name);
sub_ps.bind_texture(sampler_name,
(attribute_buf && !is_empty) ? attribute_buf : &module.dummy_vbo);
}
}
gpu::Batch *geom = pointcloud_surface_get(&pointcloud);
return geom;
}
gpu::Batch *pointcloud_sub_pass_setup(PassMain::Sub &sub_ps,
Object *object,
GPUMaterial *gpu_material)
{
return pointcloud_sub_pass_setup_implementation(sub_ps, object, gpu_material);
}
gpu::Batch *pointcloud_sub_pass_setup(PassSimple::Sub &sub_ps,
Object *object,
GPUMaterial *gpu_material)
{
return pointcloud_sub_pass_setup_implementation(sub_ps, object, gpu_material);
}
} // namespace blender::draw

View File

@@ -0,0 +1,29 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
namespace blender {
struct PointCloud;
namespace gpu {
class Batch;
class VertBuf;
} // namespace gpu
struct GPUMaterial;
namespace draw {
gpu::VertBuf *pointcloud_position_and_radius_get(PointCloud *pointcloud);
gpu::Batch **pointcloud_surface_shaded_get(PointCloud *pointcloud,
GPUMaterial **gpu_materials,
int mat_len);
gpu::Batch *pointcloud_surface_get(PointCloud *pointcloud);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_duplilist.hh"
#include "GPU_material.hh"
#include "draw_handle.hh"
#include "draw_shader_shared.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name ObjectAttributes
* \{ */
bool ObjectAttribute::sync(const draw::ObjectRef &ref,
const GPUUniformAttr &attr,
int instance_index)
{
/* This function mirrors `lookup_instance_property` in `cycles/blender/blender_object.cpp`. */
hash_code = attr.hash_code;
return ref.find_rgba_attribute(attr, instance_index, &data_x);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name LayerAttributes
* \{ */
bool LayerAttribute::sync(const Scene *scene, const ViewLayer *layer, const GPULayerAttr &attr)
{
hash_code = attr.hash_code;
return BKE_view_layer_find_rgba_attribute(scene, layer, attr.name, &data.x);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,271 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup draw
*
* Component / Object level resources like object attributes, matrices, visibility etc...
* Each of them are reference by resource index (#ResourceHandle).
*/
#include "BLI_bounds.hh"
#include "BLI_math_base.h"
#include "BLI_math_matrix.hh"
#include "BKE_curve.hh"
#include "BKE_duplilist.hh"
#include "BKE_mesh.h"
#include "BKE_object.hh"
#include "BKE_volume.hh"
#include "BLI_hash.h"
#include "DNA_curve_types.h"
#include "DNA_layer_types.h"
#include "DNA_meta_types.h"
#include "DNA_object_types.h"
#include "DRW_render.hh"
#include "draw_handle.hh"
#include "draw_shader_shared.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name ObjectMatrices
* \{ */
inline void ObjectMatrices::sync(const Object &object)
{
model = object.object_to_world();
model_inverse = object.world_to_object();
}
inline void ObjectMatrices::sync(const float4x4 &model_matrix)
{
model = model_matrix;
model_inverse = math::invert(model_matrix);
}
inline std::ostream &operator<<(std::ostream &stream, const ObjectMatrices &matrices)
{
stream << "ObjectMatrices(" << std::endl;
stream << "model=" << matrices.model << ", " << std::endl;
stream << "model_inverse=" << matrices.model_inverse << ")" << std::endl;
return stream;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name ObjectInfos
* \{ */
inline void ObjectInfos::sync()
{
object_attrs_len = 0;
object_attrs_offset = 0;
/* Zero-Initialize since this data might still be accessible (See #154105). */
orco_add = float3(0.0f);
orco_mul = float3(0.0f);
ob_color = float4(0.0f);
index = 0;
light_and_shadow_set_membership = 0;
random = 0.0f;
flag = eObjectInfoFlag::OBJECT_NO_INFO;
shadow_terminator_normal_offset = 0.0f;
shadow_terminator_geometry_offset = 0.0f;
}
inline void ObjectInfos::sync(const draw::ObjectRef ref,
bool is_active_object,
bool is_active_edit_mode)
{
object_attrs_len = 0;
object_attrs_offset = 0;
light_and_shadow_set_membership = 0;
LightLinking *light_linking = ref.light_linking();
if (light_linking) {
light_and_shadow_set_membership |= light_linking->runtime.receiver_light_set;
light_and_shadow_set_membership |= light_linking->runtime.blocker_shadow_set << 8;
}
bool is_holdout = (ref.object->base_flag & BASE_HOLDOUT) ||
(ref.object->visibility_flag & OB_HOLDOUT);
ob_color = ref.object->color;
index = ref.object->index;
SET_FLAG_FROM_TEST(flag, is_active_object, eObjectInfoFlag::OBJECT_ACTIVE);
SET_FLAG_FROM_TEST(
flag, ref.object->base_flag & BASE_SELECTED, eObjectInfoFlag::OBJECT_SELECTED);
SET_FLAG_FROM_TEST(
flag, ref.object->base_flag & BASE_FROM_DUPLI, eObjectInfoFlag::OBJECT_FROM_DUPLI);
SET_FLAG_FROM_TEST(
flag, ref.object->base_flag & BASE_FROM_SET, eObjectInfoFlag::OBJECT_FROM_SET);
SET_FLAG_FROM_TEST(
flag, ref.object->transflag & OB_NEG_SCALE, eObjectInfoFlag::OBJECT_NEGATIVE_SCALE);
SET_FLAG_FROM_TEST(flag, is_holdout, eObjectInfoFlag::OBJECT_HOLDOUT);
SET_FLAG_FROM_TEST(flag, is_active_edit_mode, eObjectInfoFlag::OBJECT_ACTIVE_EDIT_MODE);
if (ref.object->shadow_terminator_normal_offset > 0.0f) {
using namespace blender::math;
shadow_terminator_geometry_offset = ref.object->shadow_terminator_geometry_offset;
shadow_terminator_normal_offset = ref.object->shadow_terminator_normal_offset *
reduce_max(to_scale(ref.object->object_to_world()));
}
else {
shadow_terminator_geometry_offset = 0.0f;
shadow_terminator_normal_offset = 0.0f;
}
random = ref.random(0);
if (ref.object->data == nullptr) {
orco_add = float3(0.0f);
orco_mul = float3(1.0f);
return;
}
switch (GS(reinterpret_cast<ID *>(ref.object->data)->name)) {
case ID_VO: {
std::optional<const Bounds<float3>> bounds = BKE_volume_min_max(
&DRW_object_get_data_for_drawing<const Volume>(*ref.object));
if (bounds) {
orco_add = math::midpoint(bounds->min, bounds->max);
orco_mul = (bounds->max - bounds->min) * 0.5f;
}
else {
orco_add = float3(0.0f);
orco_mul = float3(1.0f);
}
break;
}
case ID_ME: {
BKE_mesh_texspace_get(
&DRW_object_get_data_for_drawing<Mesh>(*ref.object), orco_add, orco_mul);
break;
}
case ID_CU_LEGACY: {
Curve &cu = DRW_object_get_data_for_drawing<Curve>(*ref.object);
BKE_curve_texspace_ensure(&cu);
orco_add = cu.texspace_location;
orco_mul = cu.texspace_size;
break;
}
case ID_MB: {
MetaBall &mb = DRW_object_get_data_for_drawing<MetaBall>(*ref.object);
orco_add = mb.texspace_location;
orco_mul = mb.texspace_size;
break;
}
default:
orco_add = float3(0.0f);
orco_mul = float3(1.0f);
break;
}
}
inline std::ostream &operator<<(std::ostream &stream, const ObjectInfos &infos)
{
stream << "ObjectInfos(";
if (infos.flag == eObjectInfoFlag::OBJECT_NO_INFO) {
stream << "skipped)" << std::endl;
return stream;
}
stream << "orco_add=" << infos.orco_add << ", ";
stream << "orco_mul=" << infos.orco_mul << ", ";
stream << "ob_color=" << infos.ob_color << ", ";
stream << "index=" << infos.index << ", ";
stream << "random=" << infos.random << ", ";
stream << "flag=" << infos.flag << ")" << std::endl;
return stream;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name ObjectBounds
* \{ */
inline void ObjectBounds::sync()
{
#ifndef NDEBUG
/* Initialize to NaN for easier debugging of uninitialized data usage. */
bounding_corners[0] = float4(NAN_FLT);
bounding_corners[1] = float4(NAN_FLT);
bounding_corners[2] = float4(NAN_FLT);
bounding_corners[3] = float4(NAN_FLT);
bounding_sphere = float4(NAN_FLT);
#endif
bounding_sphere.w = -1.0f; /* Disable test. */
}
inline void ObjectBounds::sync(const Object &ob, float inflate_bounds)
{
const std::optional<Bounds<float3>> bounds = BKE_object_boundbox_get(&ob);
if (!bounds) {
#ifndef NDEBUG
/* Initialize to NaN for easier debugging of uninitialized data usage. */
bounding_corners[0] = float4(NAN_FLT);
bounding_corners[1] = float4(NAN_FLT);
bounding_corners[2] = float4(NAN_FLT);
bounding_corners[3] = float4(NAN_FLT);
bounding_sphere = float4(NAN_FLT);
#endif
bounding_sphere.w = -1.0f; /* Disable test. */
return;
}
const std::array<float3, 8> corners = bounds::corners(*bounds);
*reinterpret_cast<float3 *>(&bounding_corners[0]) = corners[0];
*reinterpret_cast<float3 *>(&bounding_corners[1]) = corners[4];
*reinterpret_cast<float3 *>(&bounding_corners[2]) = corners[3];
*reinterpret_cast<float3 *>(&bounding_corners[3]) = corners[1];
bounding_sphere.w = 0.0f; /* Enable test. */
if (inflate_bounds != 0.0f) {
BLI_assert(inflate_bounds >= 0.0f);
float p = inflate_bounds;
float n = -inflate_bounds;
bounding_corners[0] += float4(n, n, n, 0.0f);
bounding_corners[1] += float4(p, n, n, 0.0f);
bounding_corners[2] += float4(n, p, n, 0.0f);
bounding_corners[3] += float4(n, n, p, 0.0f);
}
}
inline void ObjectBounds::sync(const float3 &center, const float3 &size)
{
*reinterpret_cast<float3 *>(&bounding_corners[0]) = center - size;
*reinterpret_cast<float3 *>(&bounding_corners[1]) = center + float3(+size.x, -size.y, -size.z);
*reinterpret_cast<float3 *>(&bounding_corners[2]) = center + float3(-size.x, +size.y, -size.z);
*reinterpret_cast<float3 *>(&bounding_corners[3]) = center + float3(-size.x, -size.y, +size.z);
bounding_sphere.w = 0.0; /* Enable test. */
}
inline std::ostream &operator<<(std::ostream &stream, const ObjectBounds &bounds)
{
stream << "ObjectBounds(";
if (bounds.bounding_sphere.w == -1.0f) {
stream << "skipped)" << std::endl;
return stream;
}
stream << std::endl;
stream << ".bounding_corners[0]"
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[0]) << std::endl;
stream << ".bounding_corners[1]"
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[1]) << std::endl;
stream << ".bounding_corners[2]"
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[2]) << std::endl;
stream << ".bounding_corners[3]"
<< *reinterpret_cast<const float3 *>(&bounds.bounding_corners[3]) << std::endl;
stream << ".sphere=(pos=" << float3(bounds.bounding_sphere)
<< ", rad=" << bounds.bounding_sphere.w << std::endl;
stream << ")" << std::endl;
return stream;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,191 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "draw_sculpt.hh"
#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
#include "draw_attributes.hh"
#include "draw_context_private.hh"
#include "draw_view.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_customdata.hh"
#include "BKE_material.hh"
#include "BKE_object.hh"
#include "BKE_object_types.hh"
#include "BKE_paint.hh"
#include "BLI_math_matrix.hh"
#include "bmesh_class.hh"
#include "DRW_pbvh.hh"
#include "DRW_render.hh"
namespace blender::draw {
float3 SculptBatch::debug_color()
{
static float3 colors[9] = {
{1.0f, 0.2f, 0.2f},
{0.2f, 1.0f, 0.2f},
{0.2f, 0.2f, 1.0f},
{1.0f, 1.0f, 0.2f},
{0.2f, 1.0f, 1.0f},
{1.0f, 0.2f, 1.0f},
{1.0f, 0.7f, 0.2f},
{0.2f, 1.0f, 0.7f},
{0.7f, 0.2f, 1.0f},
};
return colors[debug_index % 9];
}
static Vector<SculptBatch> sculpt_batches_get_ex(const Object *ob,
const bool use_wire,
const Span<pbvh::AttributeRequest> attrs)
{
/* pbvh::Tree should always exist for non-empty meshes, created by depsgraph eval. */
bke::pbvh::Tree *pbvh = ob->runtime->sculpt_session ?
const_cast<bke::pbvh::Tree *>(bke::object::pbvh_get(*ob)) :
nullptr;
if (!pbvh) {
return {};
}
/* TODO(Miguel Pozo): Don't use global context. */
const DRWContext *drwctx = DRW_context_get();
RegionView3D *rv3d = drwctx->rv3d;
const bool navigating = rv3d && (rv3d->rflag & RV3D_NAVIGATING);
Paint *paint = nullptr;
if (drwctx->evil_C != nullptr) {
paint = BKE_paint_get_active_from_context(drwctx->evil_C);
}
/* TODO: take into account partial redraw for clipping planes. */
/* Frustum planes to show only visible pbvh::Tree nodes. */
std::array<float4, 6> draw_frustum_planes = View::default_get().frustum_planes_get();
/* Transform clipping planes to object space. Transforming a plane with a
* 4x4 matrix is done by multiplying with the transpose inverse.
* The inverse cancels out here since we transform by inverse(obmat). */
float4x4 tmat = math::transpose(ob->object_to_world());
for (int i : IndexRange(draw_frustum_planes.size())) {
draw_frustum_planes[i] = tmat * draw_frustum_planes[i];
}
/* Fast mode to show low poly multires while navigating. */
bool fast_mode = false;
if (paint && (paint->flags & PAINT_FAST_NAVIGATE)) {
fast_mode = navigating;
}
/* Update draw buffers only for visible nodes while painting.
* But do update them otherwise so navigating stays smooth. */
bool update_only_visible = rv3d && !(rv3d->rflag & RV3D_PAINTING);
if (paint && (paint->flags & PAINT_SCULPT_DELAY_UPDATES)) {
update_only_visible = true;
}
bke::pbvh::update_normals_from_eval(*const_cast<Object *>(ob), *pbvh);
pbvh::DrawCache &draw_data = pbvh::ensure_draw_data(pbvh->draw_data);
IndexMaskMemory memory;
const IndexMask visible_nodes = bke::pbvh::search_nodes(
*pbvh, memory, [&](const bke::pbvh::Node &node) {
return !BKE_pbvh_node_fully_hidden_get(node) &&
bke::pbvh::node_frustum_contain_aabb(node, draw_frustum_planes);
});
const IndexMask nodes_to_update = update_only_visible ? visible_nodes :
bke::pbvh::all_leaf_nodes(*pbvh, memory);
Span<gpu::Batch *> batches;
if (use_wire) {
batches = draw_data.ensure_lines_batches(*ob, {{}, fast_mode}, nodes_to_update);
}
else {
batches = draw_data.ensure_tris_batches(*ob, {attrs, fast_mode}, nodes_to_update);
}
const Span<int> material_indices = draw_data.ensure_material_indices(*ob);
const int max_material = std::max(0, BKE_object_material_count_eval(ob) - 1);
Vector<SculptBatch> result_batches(visible_nodes.size());
visible_nodes.foreach_index([&](const int i, const int pos) {
result_batches[pos] = {};
result_batches[pos].batch = batches[i];
result_batches[pos].material_slot = material_indices.is_empty() ?
0 :
std::clamp(material_indices[i], 0, max_material);
result_batches[pos].debug_index = pos;
});
return result_batches;
}
Vector<SculptBatch> sculpt_batches_get(const Object *ob, SculptBatchFeature features)
{
Vector<pbvh::AttributeRequest, 16> attrs;
attrs.append(pbvh::CustomRequest::Position);
attrs.append(pbvh::CustomRequest::Normal);
if (features & SCULPT_BATCH_MASK) {
attrs.append(pbvh::CustomRequest::Mask);
}
if (features & SCULPT_BATCH_FACE_SET) {
attrs.append(pbvh::CustomRequest::FaceSet);
}
const Mesh *mesh = BKE_object_get_original_mesh(ob);
if (features & SCULPT_BATCH_VERTEX_COLOR) {
if (const char *name = mesh->active_color_attribute) {
attrs.append(pbvh::GenericRequest(name));
}
}
if (features & SCULPT_BATCH_UV) {
const StringRef uv_name = mesh->active_uv_map_name();
if (!uv_name.is_empty()) {
attrs.append(pbvh::GenericRequest(uv_name));
}
}
return sculpt_batches_get_ex(ob, features & SCULPT_BATCH_WIREFRAME, attrs);
}
Vector<SculptBatch> sculpt_batches_per_material_get(const Object *ob,
Span<const GPUMaterial *> materials)
{
BLI_assert(ob->type == OB_MESH);
const Mesh &mesh = DRW_object_get_data_for_drawing<Mesh>(*ob);
VectorSet<std::string> draw_attrs;
DRW_MeshCDMask cd_needed;
DRW_mesh_get_attributes(*ob, mesh, materials, &draw_attrs, &cd_needed);
Vector<pbvh::AttributeRequest, 16> attrs;
attrs.append(pbvh::CustomRequest::Position);
attrs.append(pbvh::CustomRequest::Normal);
for (const StringRef name : draw_attrs) {
attrs.append(pbvh::GenericRequest(name));
}
for (const StringRef name : cd_needed.uv) {
attrs.append(pbvh::GenericRequest(name));
}
return sculpt_batches_get_ex(ob, false, attrs);
}
} // namespace blender::draw

View File

@@ -0,0 +1,50 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_vector.hh"
namespace blender {
struct GPUMaterial;
struct Object;
namespace gpu {
class Batch;
}
namespace draw {
struct SculptBatch {
gpu::Batch *batch;
int material_slot;
int debug_index;
float3 debug_color();
};
enum SculptBatchFeature {
SCULPT_BATCH_DEFAULT = 0,
SCULPT_BATCH_WIREFRAME = 1 << 0,
SCULPT_BATCH_MASK = 1 << 1,
SCULPT_BATCH_FACE_SET = 1 << 2,
SCULPT_BATCH_VERTEX_COLOR = 1 << 3,
SCULPT_BATCH_UV = 1 << 4
};
ENUM_OPERATORS(SculptBatchFeature);
/** Used by engines that don't use GPUMaterials, like the Workbench and Overlay engines. */
Vector<SculptBatch> sculpt_batches_get(const Object *ob, SculptBatchFeature features);
/** Used by EEVEE. */
Vector<SculptBatch> sculpt_batches_per_material_get(const Object *ob,
Span<const GPUMaterial *> materials);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,484 @@
/* SPDX-FileCopyrightText: 2019 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*
* Utilities to read id buffer created in select_engine.
*/
#include <cfloat>
#include "BLI_math_matrix.hh"
#include "MEM_guardedalloc.h"
#include "BLI_array_utils.h"
#include "BLI_bitmap.h"
#include "BLI_bitmap_draw_2d.h"
#include "BLI_math_matrix.h"
#include "BLI_rect.h"
#include "DNA_layer_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_view3d_types.h"
#include "GPU_framebuffer.hh"
#include "GPU_select.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "DRW_engine.hh"
#include "DRW_render.hh"
#include "DRW_select_buffer.hh"
#include "../engines/select/select_engine.hh"
namespace blender {
bool SELECTID_Context::is_dirty(Depsgraph *depsgraph, RegionView3D *rv3d)
{
uint64_t last_update = this->depsgraph_last_update;
this->depsgraph_last_update = DEG_get_update_count(depsgraph);
/* Check if the viewport has changed.
* This can happen when triggering the selection operator *while* playing back animation and
* looking through an animated camera. */
if (!math::is_equal(this->persmat, float4x4(rv3d->persmat), FLT_EPSILON)) {
return true;
}
/* Check if any of the drawn objects have been transformed.
* This can happen when triggering the selection operator *while* playing back animation on an
* edited mesh. */
for (Object *obj_eval : this->objects) {
if (obj_eval->runtime->last_update_transform > last_update) {
return true;
}
}
return false;
}
/* -------------------------------------------------------------------- */
/** \name Buffer of select ID's
* \{ */
uint *DRW_select_buffer_read(
Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect, uint *r_buf_len)
{
uint *buf = nullptr;
uint buf_len = 0;
/* Clamp rect. */
rcti r{};
r.xmin = 0;
r.xmax = region->winx;
r.ymin = 0;
r.ymax = region->winy;
/* Make sure that the rect is within the bounds of the viewport.
* Some GPUs have problems reading pixels off limits. */
rcti rect_clamp = *rect;
if (BLI_rcti_isect(&r, &rect_clamp, &rect_clamp) && !BLI_rcti_is_empty(&rect_clamp)) {
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
DRW_gpu_context_enable();
if (select_ctx->is_dirty(depsgraph, rv3d)) {
/* Update drawing. */
DRW_draw_select_id(depsgraph, region, v3d);
}
if (select_ctx->max_index_drawn_len > 1) {
BLI_assert(region->winx == GPU_texture_width(DRW_engine_select_texture_get()) &&
region->winy == GPU_texture_height(DRW_engine_select_texture_get()));
/* Read the UI32 pixels. */
buf_len = BLI_rcti_size_x(rect) * BLI_rcti_size_y(rect);
buf = MEM_new_array_uninitialized<uint>(buf_len, __func__);
gpu::FrameBuffer *select_id_fb = DRW_engine_select_framebuffer_get();
GPU_framebuffer_bind(select_id_fb);
GPU_framebuffer_read_color(select_id_fb,
rect_clamp.xmin,
rect_clamp.ymin,
BLI_rcti_size_x(&rect_clamp),
BLI_rcti_size_y(&rect_clamp),
1,
0,
GPU_DATA_UINT,
buf);
if (!BLI_rcti_compare(rect, &rect_clamp)) {
/* The rect has been clamped so we need to realign the buffer and fill in the blanks */
GPU_select_buffer_stride_realign(rect, &rect_clamp, buf);
}
}
GPU_framebuffer_restore();
DRW_gpu_context_disable();
}
if (r_buf_len) {
*r_buf_len = buf_len;
}
return buf;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Bitmap from ID's
*
* Given a buffer of select ID's, fill in a booleans (true/false) per index.
* #BLI_bitmap is used for memory efficiency.
*
* \{ */
uint *DRW_select_buffer_bitmap_from_rect(
Depsgraph *depsgraph, ARegion *region, View3D *v3d, const rcti *rect, uint *r_bitmap_len)
{
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
rcti rect_px = *rect;
rect_px.xmax += 1;
rect_px.ymax += 1;
uint buf_len;
uint *buf = DRW_select_buffer_read(depsgraph, region, v3d, &rect_px, &buf_len);
if (buf == nullptr) {
return nullptr;
}
BLI_assert(select_ctx->max_index_drawn_len > 0);
const uint bitmap_len = select_ctx->max_index_drawn_len - 1;
BLI_bitmap *bitmap_buf = BLI_BITMAP_NEW(bitmap_len, __func__);
const uint *buf_iter = buf;
while (buf_len--) {
const uint index = *buf_iter - 1;
if (index < bitmap_len) {
BLI_BITMAP_ENABLE(bitmap_buf, index);
}
buf_iter++;
}
MEM_delete(buf);
if (r_bitmap_len) {
*r_bitmap_len = bitmap_len;
}
return bitmap_buf;
}
uint *DRW_select_buffer_bitmap_from_circle(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int center[2],
const int radius,
uint *r_bitmap_len)
{
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
rcti rect{};
rect.xmin = center[0] - radius;
rect.xmax = center[0] + radius + 1;
rect.ymin = center[1] - radius;
rect.ymax = center[1] + radius + 1;
const uint *buf = DRW_select_buffer_read(depsgraph, region, v3d, &rect, nullptr);
if (buf == nullptr) {
return nullptr;
}
BLI_assert(select_ctx->max_index_drawn_len > 0);
const uint bitmap_len = select_ctx->max_index_drawn_len - 1;
BLI_bitmap *bitmap_buf = BLI_BITMAP_NEW(bitmap_len, __func__);
const uint *buf_iter = buf;
const int radius_sq = radius * radius;
for (int yc = -radius; yc <= radius; yc++) {
for (int xc = -radius; xc <= radius; xc++, buf_iter++) {
if (xc * xc + yc * yc < radius_sq) {
/* Intentionally wrap to max value if this is zero. */
const uint index = *buf_iter - 1;
if (index < bitmap_len) {
BLI_BITMAP_ENABLE(bitmap_buf, index);
}
}
}
}
MEM_delete(buf);
if (r_bitmap_len) {
*r_bitmap_len = bitmap_len;
}
return bitmap_buf;
}
struct PolyMaskData {
BLI_bitmap *px;
int width;
};
static void drw_select_mask_px_cb(int x, int x_end, int y, void *user_data)
{
PolyMaskData *data = static_cast<PolyMaskData *>(user_data);
BLI_bitmap *px = data->px;
int i = (y * data->width) + x;
do {
BLI_BITMAP_ENABLE(px, i);
i++;
} while (++x != x_end);
}
uint *DRW_select_buffer_bitmap_from_poly(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const Span<int2> poly,
const rcti *rect,
uint *r_bitmap_len)
{
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
rcti rect_px = *rect;
rect_px.xmax += 1;
rect_px.ymax += 1;
uint buf_len;
uint *buf = DRW_select_buffer_read(depsgraph, region, v3d, &rect_px, &buf_len);
if (buf == nullptr) {
return nullptr;
}
BLI_bitmap *buf_mask = BLI_BITMAP_NEW(buf_len, __func__);
PolyMaskData poly_mask_data;
poly_mask_data.px = buf_mask;
poly_mask_data.width = (rect->xmax - rect->xmin) + 1;
BLI_bitmap_draw_2d_poly_v2i_n(rect_px.xmin,
rect_px.ymin,
rect_px.xmax,
rect_px.ymax,
poly,
drw_select_mask_px_cb,
&poly_mask_data);
BLI_assert(select_ctx->max_index_drawn_len > 0);
const uint bitmap_len = select_ctx->max_index_drawn_len - 1;
BLI_bitmap *bitmap_buf = BLI_BITMAP_NEW(bitmap_len, __func__);
const uint *buf_iter = buf;
int i = 0;
while (buf_len--) {
const uint index = *buf_iter - 1;
if (index < bitmap_len && BLI_BITMAP_TEST(buf_mask, i)) {
BLI_BITMAP_ENABLE(bitmap_buf, index);
}
buf_iter++;
i++;
}
MEM_delete(buf);
MEM_delete(buf_mask);
if (r_bitmap_len) {
*r_bitmap_len = bitmap_len;
}
return bitmap_buf;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Find Single Select ID's
*
* Given a buffer of select ID's, find the a single select id.
*
* \{ */
uint DRW_select_buffer_sample_point(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int center[2])
{
uint ret = 0;
rcti rect{};
rect.xmin = center[0];
rect.xmax = center[0] + 1;
rect.ymin = center[1];
rect.ymax = center[1] + 1;
uint buf_len;
uint *buf = DRW_select_buffer_read(depsgraph, region, v3d, &rect, &buf_len);
if (buf) {
BLI_assert(0 != buf_len);
ret = buf[0];
MEM_delete(buf);
}
return ret;
}
struct SelectReadData {
const void *val_ptr;
uint id_min;
uint id_max;
uint r_index;
};
static bool select_buffer_test_fn(const void *__restrict value, void *__restrict userdata)
{
SelectReadData *data = static_cast<SelectReadData *>(userdata);
uint hit_id = *static_cast<uint *>(const_cast<void *>(value));
if (hit_id && hit_id >= data->id_min && hit_id < data->id_max) {
/* Start at 1 to confirm. */
data->val_ptr = value;
data->r_index = (hit_id - data->id_min) + 1;
return true;
}
return false;
}
uint DRW_select_buffer_find_nearest_to_point(Depsgraph *depsgraph,
ARegion *region,
View3D *v3d,
const int center[2],
const uint id_min,
const uint id_max,
uint *dist)
{
/* Create region around center (typically the mouse cursor).
* This must be square and have an odd width. */
rcti rect;
BLI_rcti_init_pt_radius(&rect, center, *dist);
rect.xmax += 1;
rect.ymax += 1;
int width = BLI_rcti_size_x(&rect);
int height = width;
/* Read from selection framebuffer. */
uint buf_len;
const uint *buf = DRW_select_buffer_read(depsgraph, region, v3d, &rect, &buf_len);
if (buf == nullptr) {
return 0;
}
const int shape[2] = {height, width};
const int center_yx[2] = {(height - 1) / 2, (width - 1) / 2};
SelectReadData data = {nullptr, id_min, id_max, 0};
BLI_array_iter_spiral_square(buf, shape, center_yx, select_buffer_test_fn, &data);
if (data.val_ptr) {
size_t offset = (size_t(data.val_ptr) - size_t(buf)) / sizeof(*buf);
int hit_x = offset % width;
int hit_y = offset / width;
*dist = uint(abs(hit_y - center_yx[0]) + abs(hit_x - center_yx[1]));
}
MEM_delete(buf);
return data.r_index;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Object Utils
* \{ */
bool DRW_select_buffer_elem_get(const uint sel_id,
uint &r_elem,
uint &r_base_index,
char &r_elem_type)
{
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
for (const auto &item : select_ctx->elem_ranges.items()) {
const ElemIndexRanges &ranges = item.value;
Object *ob = item.key;
if (!ranges.total.contains(sel_id)) {
continue;
}
if (ranges.face.contains(sel_id)) {
r_elem = sel_id - ranges.face.start();
r_elem_type = SCE_SELECT_FACE;
r_base_index = select_ctx->objects.first_index_of_try(ob);
return r_base_index != -1;
}
if (ranges.edge.contains(sel_id)) {
r_elem = sel_id - ranges.edge.start();
r_elem_type = SCE_SELECT_EDGE;
r_base_index = select_ctx->objects.first_index_of_try(ob);
return r_base_index != -1;
}
if (ranges.vert.contains(sel_id)) {
r_elem = sel_id - ranges.vert.start();
r_elem_type = SCE_SELECT_VERTEX;
r_base_index = select_ctx->objects.first_index_of_try(ob);
return r_base_index != -1;
}
}
return false;
}
uint DRW_select_buffer_context_offset_for_object_elem(Depsgraph *depsgraph,
Object *object,
char elem_type)
{
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
Object *ob_eval = DEG_get_evaluated(depsgraph, object);
const ElemIndexRanges base_ofs = select_ctx->elem_ranges.lookup_default(ob_eval,
ElemIndexRanges{});
if (elem_type == SCE_SELECT_VERTEX) {
return base_ofs.vert.start();
}
if (elem_type == SCE_SELECT_EDGE) {
return base_ofs.edge.start();
}
if (elem_type == SCE_SELECT_FACE) {
return base_ofs.face.start();
}
BLI_assert(0);
return 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Context
* \{ */
void DRW_select_buffer_context_create(Depsgraph *depsgraph,
const Span<Base *> bases,
short select_mode)
{
SELECTID_Context *select_ctx = DRW_select_engine_context_get();
select_ctx->objects.reinitialize(bases.size());
for (const int i : bases.index_range()) {
Object *obj = bases[i]->object;
select_ctx->objects[i] = DEG_get_evaluated(depsgraph, obj);
}
select_ctx->select_mode = select_mode;
select_ctx->persmat = float4x4::zero();
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,238 @@
/* SPDX-FileCopyrightText: 2020 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw_engine
*/
#include "draw_shader.hh"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "opensubdiv_capi_type.hh"
#include "opensubdiv_evaluator_capi.hh"
#include "DRW_render.hh"
namespace blender {
#define SHADER_CUSTOM_DATA_INTERP_MAX_DIMENSIONS 4
static StringRefNull get_subdiv_shader_info_name(SubdivShaderType shader_type)
{
switch (shader_type) {
case SubdivShaderType::BUFFER_LINES:
return "subdiv_lines";
case SubdivShaderType::BUFFER_LINES_LOOSE:
return "subdiv_lines_loose";
case SubdivShaderType::BUFFER_TRIS:
return "subdiv_tris_single_material";
case SubdivShaderType::BUFFER_TRIS_MULTIPLE_MATERIALS:
return "subdiv_tris_multiple_materials";
case SubdivShaderType::BUFFER_EDGE_FAC:
return "subdiv_edge_fac";
case SubdivShaderType::BUFFER_SCULPT_DATA:
return "subdiv_sculpt_data";
case SubdivShaderType::PATCH_EVALUATION:
return "subdiv_patch_evaluation_verts";
case SubdivShaderType::PATCH_EVALUATION_FVAR:
return "subdiv_patch_evaluation_fvar";
case SubdivShaderType::PATCH_EVALUATION_FACE_DOTS:
return "subdiv_patch_evaluation_fdots";
case SubdivShaderType::PATCH_EVALUATION_FACE_DOTS_WITH_NORMALS:
return "subdiv_patch_evaluation_fdots_normals";
case SubdivShaderType::PATCH_EVALUATION_ORCO:
return "subdiv_patch_evaluation_verts_orcos";
case SubdivShaderType::BUFFER_UV_STRETCH_ANGLE:
return "subdiv_edituv_stretch_angle";
case SubdivShaderType::BUFFER_UV_STRETCH_AREA:
return "subdiv_edituv_stretch_area";
case SubdivShaderType::BUFFER_NORMALS_ACCUMULATE:
return "subdiv_normals_accumulate";
case SubdivShaderType::BUFFER_PAINT_OVERLAY_FLAG:
return "subdiv_paint_overlay_flag";
case SubdivShaderType::BUFFER_LNOR:
return "subdiv_loop_normals";
case SubdivShaderType::COMP_CUSTOM_DATA_INTERP:
break;
default:
break;
}
BLI_assert_unreachable();
return "";
}
namespace draw::Shader {
class ShaderCache {
static gpu::StaticShaderCache<ShaderCache> &get_static_cache()
{
static gpu::StaticShaderCache<ShaderCache> static_cache;
return static_cache;
}
public:
static ShaderCache &get()
{
return get_static_cache().get();
}
static void release()
{
get_static_cache().release();
}
gpu::StaticShader curves_topology = {"draw_curves_topology"};
gpu::StaticShader curves_evaluate_position = {"draw_curves_interpolate_position"};
gpu::StaticShader curves_evaluate_float4 = {"draw_curves_interpolate_float4_attribute"};
gpu::StaticShader curves_evaluate_float3 = {"draw_curves_interpolate_float3_attribute"};
gpu::StaticShader curves_evaluate_float2 = {"draw_curves_interpolate_float2_attribute"};
gpu::StaticShader curves_evaluate_float = {"draw_curves_interpolate_float_attribute"};
gpu::StaticShader curves_evaluate_length_intercept = {"draw_curves_evaluate_length_intercept"};
gpu::StaticShader debug_draw_display = {"draw_debug_draw_display"};
gpu::StaticShader draw_visibility_compute = {"draw_visibility_compute"};
gpu::StaticShader draw_view_finalize = {"draw_view_finalize"};
gpu::StaticShader draw_resource_finalize = {"draw_resource_finalize"};
gpu::StaticShader draw_command_generate = {"draw_command_generate"};
gpu::StaticShader subdiv_sh[SUBDIVISION_MAX_SHADERS];
gpu::StaticShader subdiv_custom_data_sh[SHADER_CUSTOM_DATA_INTERP_MAX_DIMENSIONS][GPU_COMP_MAX];
gpu::StaticShader subdiv_interp_corner_normals_sh = {
"subdiv_custom_data_interp_3d_f32_normalize"};
ShaderCache()
{
for (int i : IndexRange(SUBDIVISION_MAX_SHADERS)) {
if (SubdivShaderType(i) == SubdivShaderType::COMP_CUSTOM_DATA_INTERP) {
continue;
}
subdiv_sh[i] = {get_subdiv_shader_info_name(SubdivShaderType(i)).c_str()};
}
for (int dimension : IndexRange(SHADER_CUSTOM_DATA_INTERP_MAX_DIMENSIONS)) {
for (GPUVertCompType comp_type : {GPU_COMP_U16, GPU_COMP_I32, GPU_COMP_F32}) {
std::string info_name = "subdiv_custom_data_interp";
const char *dimension_names[] = {"_1d", "_2d", "_3d", "_4d"};
info_name += dimension_names[dimension];
switch (comp_type) {
case GPU_COMP_U16:
info_name += "_u16";
break;
case GPU_COMP_I32:
info_name += "_i32";
break;
case GPU_COMP_F32:
info_name += "_f32";
break;
default:
BLI_assert_unreachable();
}
subdiv_custom_data_sh[dimension][comp_type] = {info_name};
}
}
}
};
} // namespace draw::Shader
using namespace blender::draw::Shader;
gpu::Shader *DRW_shader_curves_topology_get()
{
return ShaderCache::get().curves_topology.get();
}
gpu::Shader *DRW_shader_curves_refine_get(draw::CurvesEvalShader type)
{
switch (type) {
case draw::CURVES_EVAL_POSITION:
return ShaderCache::get().curves_evaluate_position.get();
case draw::CURVES_EVAL_FLOAT4:
return ShaderCache::get().curves_evaluate_float4.get();
case draw::CURVES_EVAL_FLOAT3:
return ShaderCache::get().curves_evaluate_float3.get();
case draw::CURVES_EVAL_FLOAT2:
return ShaderCache::get().curves_evaluate_float2.get();
case draw::CURVES_EVAL_FLOAT:
return ShaderCache::get().curves_evaluate_float.get();
case draw::CURVES_EVAL_LENGTH_INTERCEPT:
return ShaderCache::get().curves_evaluate_length_intercept.get();
}
BLI_assert_unreachable();
return nullptr;
}
gpu::Shader *DRW_shader_debug_draw_display_get()
{
return ShaderCache::get().debug_draw_display.get();
}
gpu::Shader *DRW_shader_draw_visibility_compute_get()
{
return ShaderCache::get().draw_visibility_compute.get();
}
gpu::Shader *DRW_shader_draw_view_finalize_get()
{
return ShaderCache::get().draw_view_finalize.get();
}
gpu::Shader *DRW_shader_draw_resource_finalize_get()
{
return ShaderCache::get().draw_resource_finalize.get();
}
gpu::Shader *DRW_shader_draw_command_generate_get()
{
return ShaderCache::get().draw_command_generate.get();
}
gpu::Shader *DRW_shader_subdiv_get(SubdivShaderType shader_type)
{
BLI_assert(!ELEM(shader_type, SubdivShaderType::COMP_CUSTOM_DATA_INTERP));
return ShaderCache::get().subdiv_sh[uint(shader_type)].get();
}
gpu::Shader *DRW_shader_subdiv_custom_data_get(GPUVertCompType comp_type, int dimensions)
{
BLI_assert(dimensions >= 1 && dimensions <= SHADER_CUSTOM_DATA_INTERP_MAX_DIMENSIONS);
if (comp_type == GPU_COMP_U16) {
BLI_assert(dimensions == 4);
}
BLI_assert(ELEM(comp_type, GPU_COMP_U16, GPU_COMP_I32, GPU_COMP_F32));
return ShaderCache::get().subdiv_custom_data_sh[dimensions - 1][comp_type].get();
}
gpu::Shader *DRW_shader_subdiv_interp_corner_normals_get()
{
return ShaderCache::get().subdiv_interp_corner_normals_sh.get();
}
void DRW_shaders_free()
{
GPU_shader_unbind();
ShaderCache::release();
}
} // namespace blender

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "draw_curves_private.hh"
#include "draw_hair_private.hh"
namespace blender {
namespace gpu {
class Shader;
} // namespace gpu
/* draw_shader.cc */
gpu::Shader *DRW_shader_curves_topology_get();
gpu::Shader *DRW_shader_curves_refine_get(draw::CurvesEvalShader type);
gpu::Shader *DRW_shader_debug_draw_display_get();
gpu::Shader *DRW_shader_draw_visibility_compute_get();
gpu::Shader *DRW_shader_draw_view_finalize_get();
gpu::Shader *DRW_shader_draw_resource_finalize_get();
gpu::Shader *DRW_shader_draw_command_generate_get();
/* Subdivision */
enum class SubdivShaderType {
BUFFER_LINES = 0,
BUFFER_LINES_LOOSE = 1,
BUFFER_EDGE_FAC = 2,
BUFFER_LNOR = 3,
BUFFER_TRIS = 4,
BUFFER_TRIS_MULTIPLE_MATERIALS = 5,
BUFFER_NORMALS_ACCUMULATE = 6,
BUFFER_PAINT_OVERLAY_FLAG = 7,
PATCH_EVALUATION = 8,
PATCH_EVALUATION_FVAR = 9,
PATCH_EVALUATION_FACE_DOTS = 10,
PATCH_EVALUATION_FACE_DOTS_WITH_NORMALS = 11,
PATCH_EVALUATION_ORCO = 12,
COMP_CUSTOM_DATA_INTERP = 13,
BUFFER_SCULPT_DATA = 14,
BUFFER_UV_STRETCH_ANGLE = 15,
BUFFER_UV_STRETCH_AREA = 16,
};
constexpr int SUBDIVISION_MAX_SHADERS = 17;
gpu::Shader *DRW_shader_subdiv_get(SubdivShaderType shader_type);
gpu::Shader *DRW_shader_subdiv_custom_data_get(GPUVertCompType comp_type, int dimensions);
gpu::Shader *DRW_shader_subdiv_interp_corner_normals_get();
void DRW_shaders_free();
} // namespace blender

View File

@@ -0,0 +1,713 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_shader_shared_utils.hh"
#ifndef GPU_SHADER
# include "BLI_math_vector.hh"
# include "GPU_shader.hh"
# include "draw_defines.hh"
# if defined(__cplusplus) && !defined(GPU_SHADER)
/* Defined on windows in `windows.h`. */
# undef far
# undef near
namespace blender {
# endif
struct ViewCullingData;
struct ViewMatrices;
struct ObjectMatrices;
struct ObjectInfos;
struct ObjectBounds;
struct VolumeInfos;
struct CurvesInfos;
struct ObjectAttribute;
struct LayerAttribute;
struct DrawCommand;
struct DispatchCommand;
struct DRWDebugPrintBuffer;
struct DRWDebugVertPair;
struct DRWDebugDrawBuffer;
struct FrustumCorners;
struct FrustumPlanes;
/* __cplusplus is true when compiling with MSL. */
# if defined(__cplusplus) && !defined(GPU_SHADER)
/* C++ only forward declarations. */
struct Object;
struct Scene;
struct ViewLayer;
struct GPUUniformAttr;
struct GPULayerAttr;
namespace draw {
class ObjectRef;
} // namespace draw
using namespace math;
# endif
#endif
#define DRW_SHADER_SHARED_H
#define DRW_RESOURCE_CHUNK_LEN 512
/* Define the maximum number of grid we allow in a volume UBO. */
#define DRW_GRID_PER_VOLUME_MAX 16
/* Define the maximum number of attribute we allow in a curves UBO.
* This should be kept in sync with `GPU_ATTR_MAX` */
#define DRW_ATTRIBUTE_PER_CURVES_MAX 15
/* -------------------------------------------------------------------- */
/** \name Views
* \{ */
#if !defined(DRW_VIEW_LEN) && !defined(GLSL_CPP_STUBS)
/* Single-view case (default). */
# define drw_view_id 0
# define DRW_VIEW_LEN 1
# define DRW_VIEW_SHIFT 0
# define DRW_VIEW_FROM_RESOURCE_ID
#else
/* Multi-view case. */
/** This should be already defined at shaderCreateInfo level. */
// # define DRW_VIEW_LEN 64
/** Global that needs to be set correctly in each shader stage. */
uint drw_view_id = 0;
/**
* In order to reduce the memory requirements, the view id is merged with resource id to avoid
* doubling the memory required only for view indexing.
*/
/** \note This is simply log2(DRW_VIEW_LEN) but making sure it is optimized out. */
# define DRW_VIEW_SHIFT \
((DRW_VIEW_LEN > 32) ? 6 : \
(DRW_VIEW_LEN > 16) ? 5 : \
(DRW_VIEW_LEN > 8) ? 4 : \
(DRW_VIEW_LEN > 4) ? 3 : \
(DRW_VIEW_LEN > 2) ? 2 : \
1)
# define DRW_VIEW_MASK ~(0xFFFFFFFFu << DRW_VIEW_SHIFT)
# define DRW_VIEW_FROM_RESOURCE_ID drw_view_id = (drw_resource_id_raw() & DRW_VIEW_MASK)
#endif
struct [[host_shared]] FrustumCorners {
float4 corners[8];
};
struct [[host_shared]] FrustumPlanes {
/* [0] left
* [1] right
* [2] bottom
* [3] top
* [4] near
* [5] far */
float4 planes[6];
};
struct [[host_shared]] ViewCullingData {
/** \note float3 array padded to float4. */
/** Frustum corners. */
struct FrustumCorners frustum_corners;
struct FrustumPlanes frustum_planes;
float4 bound_sphere;
};
struct [[host_shared]] ViewMatrices {
float4x4 viewmat;
float4x4 viewinv;
float4x4 winmat;
float4x4 wininv;
/* Returns true if the current view has a perspective projection matrix. */
bool is_perspective() const
{
return winmat[3][3] == 0.0f;
}
/* Returns the view forward vector, going towards the viewer. */
float3 forward() const
{
return viewinv[2].xyz();
}
/* Returns the view up vector. */
float3 up() const
{
return viewinv[1].xyz();
}
/* Returns the view origin. */
float3 position() const
{
return viewinv[3].xyz();
}
/* Positive Z distance from the view origin. Faster than using `drw_point_world_to_view`. */
float z_distance(float3 P) const
{
return dot(P - position(), -forward());
}
/* Returns the projection matrix far clip distance. */
float far() const
{
if (is_perspective()) {
return -winmat[3][2] / (winmat[2][2] + 1.0f);
}
return -(winmat[3][2] - 1.0f) / winmat[2][2];
}
/* Returns the projection matrix near clip distance. */
float near() const
{
if (is_perspective()) {
return -winmat[3][2] / (winmat[2][2] - 1.0f);
}
return -(winmat[3][2] + 1.0f) / winmat[2][2];
}
/**
* Returns the world incident vector `V` (going towards the viewer)
* from the world position `P` and the current view.
*/
float3 world_incident_vector(float3 P) const
{
return is_perspective() ? normalize(position() - P) : forward();
}
/**
* Returns the view incident vector `vV` (going towards the viewer)
* from the view position `vP` and the current view.
*/
float3 view_incident_vector(float3 vP) const
{
return is_perspective() ? normalize(-vP) : float3(0.0f, 0.0f, 1.0f);
}
/**
* Transform position on screen UV space [0..1] to Normalized Device Coordinate space [-1..1].
*/
template<typename T> T screen_to_ndc(T ss_P) const
{
return ss_P * 2.0f - 1.0f;
}
/**
* Transform position in Normalized Device Coordinate [-1..1] to screen UV space [0..1].
*/
template<typename T> T ndc_to_screen(T ndc_P) const
{
return ndc_P * 0.5f + 0.5f;
}
/* -------------------------------------------------------------------- */
/** \name Transform Normal
* \{ */
#ifdef GPU_SHADER /* `to_float3x3` doesn't exist on CPU side. */
float3 normal_view_to_world(float3 vN) const
{
return (to_float3x3(viewinv) * vN);
}
float3 normal_world_to_view(float3 N) const
{
return (to_float3x3(viewmat) * N);
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Position
* \{ */
float3 perspective_divide(float4 hs_P) const
{
return hs_P.xyz() / hs_P.w;
}
float3 point_view_to_world(float3 vP) const
{
return (viewinv * float4(vP, 1.0f)).xyz();
}
float4 point_view_to_homogenous(float3 vP) const
{
return (winmat * float4(vP, 1.0f));
}
float3 point_view_to_ndc(float3 vP) const
{
return perspective_divide(point_view_to_homogenous(vP));
}
float3 point_world_to_view(float3 P) const
{
return (viewmat * float4(P, 1.0f)).xyz();
}
float4 point_world_to_homogenous(float3 P) const
{
return (winmat * (viewmat * float4(P, 1.0f)));
}
float3 point_world_to_ndc(float3 P) const
{
return perspective_divide(point_world_to_homogenous(P));
}
float3 point_ndc_to_view(float3 ssP) const
{
return perspective_divide(wininv * float4(ssP, 1.0f));
}
float3 point_ndc_to_world(float3 ssP) const
{
return point_view_to_world(point_ndc_to_view(ssP));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Screen Positions
* \{ */
float3 point_view_to_screen(float3 vP) const
{
return ndc_to_screen(point_view_to_ndc(vP));
}
float3 point_world_to_screen(float3 vP) const
{
return ndc_to_screen(point_world_to_ndc(vP));
}
float3 point_screen_to_view(float3 ssP) const
{
return point_ndc_to_view(screen_to_ndc(ssP));
}
float3 point_screen_to_world(float3 ssP) const
{
return point_view_to_world(point_screen_to_view(ssP));
}
float depth_view_to_screen(float v_depth) const
{
return point_view_to_screen(float3(0.0f, 0.0f, v_depth)).z;
}
float depth_screen_to_view(float ss_depth) const
{
return point_screen_to_view(float3(0.0f, 0.0f, ss_depth)).z;
}
/** \} */
};
template float ViewMatrices::screen_to_ndc<float>(float ss_P) const;
template float2 ViewMatrices::screen_to_ndc<float2>(float2 ss_P) const;
template float3 ViewMatrices::screen_to_ndc<float3>(float3 ss_P) const;
template float ViewMatrices::ndc_to_screen<float>(float ss_P) const;
template float2 ViewMatrices::ndc_to_screen<float2>(float2 ss_P) const;
template float3 ViewMatrices::ndc_to_screen<float3>(float3 ss_P) const;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug draw shapes
* \{ */
struct [[host_shared]] ObjectMatrices {
float4x4 model;
float4x4 model_inverse;
#ifndef GPU_SHADER
void sync(const Object &object);
void sync(const float4x4 &model_matrix);
#endif
#ifdef GPU_SHADER /* `to_float3x3` doesn't exist on CPU side. */
/**
* Usually Normal matrix is `transpose(inverse(ViewMatrix * ModelMatrix))`.
*
* But since it is slow to multiply matrices we decompose it. Decomposing
* inversion and transposition both invert the product order leaving us with
* the same original order:
* transpose(ViewMatrixInverse) * transpose(ModelMatrixInverse)
*
* Knowing that the view matrix is orthogonal, the transpose is also the inverse.
* NOTE: This is only valid because we are only using the mat3 of the ViewMatrixInverse.
* ViewMatrix * transpose(ModelMatrixInverse)
*/
float3x3 normal() const
{
return transpose(to_float3x3(model_inverse));
}
float3x3 normal_inverse() const
{
return transpose(to_float3x3(model));
}
#endif
/* -------------------------------------------------------------------- */
/** \name Transform Normal
*
* Space conversion helpers for normal vectors.
* \{ */
#ifdef GPU_SHADER /* `to_float3x3` doesn't exist on CPU side. */
float3 normal_object_to_world(float3 lN) const
{
return normal() * lN;
}
float3 normal_world_to_object(float3 N) const
{
return normal_inverse() * N;
}
float3 normal_object_to_view(ViewMatrices view, float3 lN) const
{
return to_float3x3(view.viewmat) * (normal() * lN);
}
float3 normal_view_to_object(ViewMatrices view, float3 vN) const
{
return normal_inverse() * (to_float3x3(view.viewinv) * vN);
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform Point
*
* Space conversion helpers for points (coordinates).
* \{ */
float3 point_object_to_world(float3 lP) const
{
return (model * float4(lP, 1.0f)).xyz();
}
float3 point_world_to_object(float3 P) const
{
return (model_inverse * float4(P, 1.0f)).xyz();
}
float3 point_object_to_view(ViewMatrices view, float3 lP) const
{
return (view.viewmat * (model * float4(lP, 1.0f))).xyz();
}
float3 point_view_to_object(ViewMatrices view, float3 vP) const
{
return (model_inverse * (view.viewinv * float4(vP, 1.0f))).xyz();
}
float4 point_object_to_homogenous(ViewMatrices view, float3 lP) const
{
return view.winmat * (view.viewmat * (model * float4(lP, 1.0f)));
}
float3 point_object_to_ndc(ViewMatrices view, float3 lP) const
{
return view.perspective_divide(point_object_to_homogenous(view, lP));
}
/** \} */
};
enum [[host_shared]] eObjectInfoFlag : uint32_t {
OBJECT_SELECTED = (1u << 0u),
OBJECT_FROM_DUPLI = (1u << 1u),
OBJECT_FROM_SET = (1u << 2u),
OBJECT_ACTIVE = (1u << 3u),
OBJECT_NEGATIVE_SCALE = (1u << 4u),
OBJECT_HOLDOUT = (1u << 5u),
/* Implies all objects that match the current active object's mode and able to be edited
* simultaneously. Currently only applicable for edit mode. */
OBJECT_ACTIVE_EDIT_MODE = (1u << 6u),
/* Avoid skipped info to change culling. */
OBJECT_NO_INFO = ~OBJECT_HOLDOUT
};
#ifndef GPU_SHADER
ENUM_OPERATORS(eObjectInfoFlag);
#endif
struct [[host_shared]] ObjectInfos {
/** Uploaded as center + size. Converted to mul+bias to local coord. */
packed_float3 orco_add;
uint object_attrs_offset;
packed_float3 orco_mul;
uint object_attrs_len;
float4 ob_color;
uint index;
/** Used for Light Linking in EEVEE */
uint light_and_shadow_set_membership;
float random;
enum eObjectInfoFlag flag;
float shadow_terminator_normal_offset;
float shadow_terminator_geometry_offset;
float _pad1;
float _pad2;
#ifndef GPU_SHADER
void sync();
void sync(const draw::ObjectRef ref, bool is_active_object, bool is_active_edit_mode);
#endif
};
inline uint receiver_light_set_get(ObjectInfos object_infos)
{
return object_infos.light_and_shadow_set_membership & 0xFFu;
}
inline uint blocker_shadow_set_get(ObjectInfos object_infos)
{
return (object_infos.light_and_shadow_set_membership >> 8u) & 0xFFu;
}
struct [[host_shared]] ObjectBounds {
/**
* Uploaded as vertex (0, 4, 3, 1) of the bbox in local space, matching XYZ axis order.
* Then processed by GPU and stored as (0, 4-0, 3-0, 1-0) in world space for faster culling.
*/
float4 bounding_corners[4];
/** Bounding sphere derived from the bounding corner. Computed on GPU. */
float4 bounding_sphere;
/** Radius of the inscribed sphere derived from the bounding corner. Computed on GPU. */
#define _inner_sphere_radius bounding_corners[3].w
#ifndef GPU_SHADER
void sync();
void sync(const Object &ob, float inflate_bounds = 0.0f);
void sync(const float3 &center, const float3 &size);
#endif
};
/* Return true if `bounding_corners` are valid. Should be checked before accessing them.
* Does not guarantee that `bounding_sphere` is valid.
* Converting these bounds to an `IsectBox` may generate invalid clip planes.
* For safe `IsectBox` generation check `drw_bounds_are_valid`. */
inline bool drw_bounds_corners_are_valid(ObjectBounds bounds)
{
return bounds.bounding_sphere.w != -1.0f;
}
/* Return true if bounds are ready for culling.
* In this case, both `bounding_corners` and `bounding_sphere` are valid.
* These bounds can be safely converted to an `IsectBox` with valid clip planes. */
inline bool drw_bounds_are_valid(ObjectBounds bounds)
{
return bounds.bounding_sphere.w >= 0.0f;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Object attributes
* \{ */
struct [[host_shared]] VolumeInfos {
/** Object to grid-space. */
float4x4 grids_xform[DRW_GRID_PER_VOLUME_MAX];
/** \note float4 for alignment. Only float3 needed. */
float4 color_mul;
float density_scale;
float temperature_mul;
float temperature_bias;
float _pad;
};
struct [[host_shared]] CurvesInfos {
/* TODO(fclem): Make it a single uint. */
/** Per attribute scope, follows loading order.
* \note uint as bool in GLSL is 4 bytes.
* \note GLSL pad arrays of scalar to 16 bytes (std140). */
uint4 is_point_attribute[DRW_ATTRIBUTE_PER_CURVES_MAX];
/* Number of vertex in a segment (including restart vertex for cylinder). */
uint vertex_per_segment;
/* Edge count for the visible half cylinder. Equal to face count + 1. */
uint half_cylinder_face_count;
uint _pad0;
uint _pad1;
};
#pragma pack(push, 4)
struct [[host_shared]] ObjectAttribute {
/* Workaround the padding cost from alignment requirements.
* (see GL spec : 7.6.2.2 Standard Uniform Block Layout) */
float data_x;
float data_y;
float data_z;
float data_w;
uint hash_code;
#ifndef GPU_SHADER
/**
* Go through all possible source of the given object uniform attribute.
* Returns true if the attribute was correctly filled.
*
* \param instance_index: The index of synced the instance within the ObjectRef::duplis_,
* or just 0 if the ObjectRef doesn't contain any instances.
*/
bool sync(const draw::ObjectRef &ref, const GPUUniformAttr &attr, int instance_index);
#endif
};
#pragma pack(pop)
/** \note we only align to 4 bytes and fetch data manually so make sure
* C++ compiler gives us the same size. */
BLI_STATIC_ASSERT_ALIGN(ObjectAttribute, 20)
struct [[host_shared]] LayerAttribute {
float4 data;
uint hash_code;
uint buffer_length; /* Only in the first record. */
uint _pad1;
uint _pad2;
#ifndef GPU_SHADER
bool sync(const Scene *scene, const ViewLayer *layer, const GPULayerAttr &attr);
#endif
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Indirect commands structures.
* \{ */
/* Regular draw commands (no index buffer). */
struct [[host_shared]] DrawCommandArray {
uint vertex_len;
uint instance_len;
uint vertex_first;
uint instance_first;
uint _pad0;
uint _pad1;
uint _pad2;
uint _pad3;
};
/* Indexed draw commands (with index buffer). */
struct [[host_shared]] DrawCommandIndexed {
uint vertex_len;
uint instance_len;
uint vertex_first;
uint base_index;
uint instance_first;
uint _pad0;
uint _pad1;
uint _pad2;
};
struct [[host_shared]] DrawCommand {
union {
union_t<DrawCommandArray> array;
union_t<DrawCommandIndexed> indexed;
};
};
struct [[host_shared]] DispatchCommand {
uint num_groups_x;
uint num_groups_y;
uint num_groups_z;
uint _pad0;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug draw shapes
* \{ */
struct [[host_shared]] DRWDebugVertPair {
/* This is a weird layout, but needed to be able to use DRWDebugVertPair as
* a DrawCommand and avoid alignment issues. See drw_debug_lines_buf[] definition. */
uint pos1_x;
uint pos1_y;
uint pos1_z;
/* Named vert_color to avoid global namespace collision with uniform color. */
uint vert_color;
uint pos2_x;
uint pos2_y;
uint pos2_z;
/* Number of time this line is supposed to be displayed. Decremented by one on display. */
uint lifetime;
};
inline DRWDebugVertPair debug_line_make(uint in_pos1_x,
uint in_pos1_y,
uint in_pos1_z,
uint in_pos2_x,
uint in_pos2_y,
uint in_pos2_z,
uint in_vert_color,
uint in_lifetime)
{
DRWDebugVertPair debug_vert;
debug_vert.pos1_x = in_pos1_x;
debug_vert.pos1_y = in_pos1_y;
debug_vert.pos1_z = in_pos1_z;
debug_vert.pos2_x = in_pos2_x;
debug_vert.pos2_y = in_pos2_y;
debug_vert.pos2_z = in_pos2_z;
debug_vert.vert_color = in_vert_color;
debug_vert.lifetime = in_lifetime;
return debug_vert;
}
inline uint debug_color_pack(float4 v_color)
{
v_color = clamp(v_color, 0.0f, 1.0f);
uint result = 0;
result |= uint(v_color.x * 255.0) << 0u;
result |= uint(v_color.y * 255.0) << 8u;
result |= uint(v_color.z * 255.0) << 16u;
result |= uint(v_color.w * 255.0) << 24u;
return result;
}
/* Take the header (DrawCommand) into account. */
#define DRW_DEBUG_DRAW_VERT_MAX (2 * 1024) - 1
/* The debug draw buffer is laid-out as the following struct.
* But we use plain array in shader code instead because of driver issues. */
struct [[host_shared]] DRWDebugDrawBuffer {
struct DrawCommand command;
struct DRWDebugVertPair verts[DRW_DEBUG_DRAW_VERT_MAX];
};
/* Equivalent to `DRWDebugDrawBuffer.command.v_count`. */
#define drw_debug_draw_v_count(buf) buf[0].pos1_x
/**
* Offset to the first data. Equal to: `sizeof(DrawCommand) / sizeof(DRWDebugVertPair)`.
* This is needed because we bind the whole buffer as a `DRWDebugVertPair` array.
*/
#define drw_debug_draw_offset 1
/** \} */
#if !defined(GPU_SHADER)
} // namespace blender
#endif

View File

@@ -0,0 +1,225 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "GPU_state.hh"
#include "BLI_enum_flags.hh"
namespace blender {
/** \file
* \ingroup draw
*
* Internal Pipeline State tracking. It is higher level than GPU state as everything fits a single
* enum.
*/
/**
* DRWState is a bit-mask that stores the current render state and the desired render state. Based
* on the differences the minimum state changes can be invoked to setup the desired render state.
*
* The Write Stencil, Stencil test, Depth test and Blend state options are mutual exclusive
* therefore they aren't ordered as a bit mask.
*/
enum DRWState : uint32_t {
/** To be used for compute passes. */
DRW_STATE_NO_DRAW = 0,
/** Write mask */
DRW_STATE_WRITE_DEPTH = (1 << 0),
DRW_STATE_WRITE_COLOR = (1 << 1),
/* Write Stencil. These options are mutual exclusive and packed into 2 bits */
DRW_STATE_WRITE_STENCIL = (1 << 2),
DRW_STATE_WRITE_STENCIL_SHADOW_PASS = (2 << 2),
DRW_STATE_WRITE_STENCIL_SHADOW_FAIL = (3 << 2),
/** Depth test. These options are mutual exclusive and packed into 3 bits */
DRW_STATE_DEPTH_ALWAYS = (1 << 4),
DRW_STATE_DEPTH_LESS = (2 << 4),
DRW_STATE_DEPTH_LESS_EQUAL = (3 << 4),
DRW_STATE_DEPTH_EQUAL = (4 << 4),
DRW_STATE_DEPTH_GREATER = (5 << 4),
DRW_STATE_DEPTH_GREATER_EQUAL = (6 << 4),
/** Culling test */
DRW_STATE_CULL_BACK = (1 << 7),
DRW_STATE_CULL_FRONT = (1 << 8),
/** Stencil test. These options are mutually exclusive and packed into 2 bits. */
DRW_STATE_STENCIL_ALWAYS = (1 << 9),
DRW_STATE_STENCIL_EQUAL = (2 << 9),
DRW_STATE_STENCIL_NEQUAL = (3 << 9),
/** Blend state. These options are mutual exclusive and packed into 4 bits */
DRW_STATE_BLEND_ADD = (1 << 11),
/** Same as additive but let alpha accumulate without pre-multiply. */
DRW_STATE_BLEND_ADD_FULL = (2 << 11),
/** Standard alpha blending. */
DRW_STATE_BLEND_ALPHA = (3 << 11),
/** Use that if color is already pre-multiply by alpha. */
DRW_STATE_BLEND_ALPHA_PREMUL = (4 << 11),
DRW_STATE_BLEND_BACKGROUND = (5 << 11),
DRW_STATE_BLEND_OIT = (6 << 11),
DRW_STATE_BLEND_MUL = (7 << 11),
DRW_STATE_BLEND_SUB = (8 << 11),
/** Use dual source blending. WARNING: Only one color buffer allowed. */
DRW_STATE_BLEND_CUSTOM = (9 << 11),
DRW_STATE_LOGIC_INVERT = (10 << 11),
DRW_STATE_BLEND_ALPHA_UNDER_PREMUL = (11 << 11),
DRW_STATE_BLEND_TRANSPARENCY = (12 << 11),
/* See GPU_clip_control_unit_range. */
DRW_STATE_CLIP_CONTROL_UNIT_RANGE = (1 << 28),
DRW_STATE_CLIP_PLANES = (1 << 29),
DRW_STATE_FIRST_VERTEX_CONVENTION = (1 << 30),
/** DO NOT USE. Assumed always enabled. Only used internally. */
DRW_STATE_PROGRAM_POINT_SIZE = (1u << 31),
};
ENUM_OPERATORS(DRWState);
#define DRW_STATE_DEFAULT \
(DRW_STATE_WRITE_DEPTH | DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS_EQUAL)
#define DRW_STATE_BLEND_ENABLED \
(DRW_STATE_BLEND_ADD | DRW_STATE_BLEND_ADD_FULL | DRW_STATE_BLEND_ALPHA | \
DRW_STATE_BLEND_ALPHA_PREMUL | DRW_STATE_BLEND_BACKGROUND | DRW_STATE_BLEND_OIT | \
DRW_STATE_BLEND_MUL | DRW_STATE_BLEND_SUB | DRW_STATE_BLEND_CUSTOM | DRW_STATE_LOGIC_INVERT | \
DRW_STATE_BLEND_ALPHA_UNDER_PREMUL | DRW_STATE_BLEND_TRANSPARENCY)
#define DRW_STATE_RASTERIZER_ENABLED \
(DRW_STATE_WRITE_DEPTH | DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_STENCIL | \
DRW_STATE_WRITE_STENCIL_SHADOW_PASS | DRW_STATE_WRITE_STENCIL_SHADOW_FAIL)
#define DRW_STATE_DEPTH_TEST_ENABLED \
(DRW_STATE_DEPTH_ALWAYS | DRW_STATE_DEPTH_LESS | DRW_STATE_DEPTH_LESS_EQUAL | \
DRW_STATE_DEPTH_EQUAL | DRW_STATE_DEPTH_GREATER | DRW_STATE_DEPTH_GREATER_EQUAL)
#define DRW_STATE_STENCIL_TEST_ENABLED \
(DRW_STATE_STENCIL_ALWAYS | DRW_STATE_STENCIL_EQUAL | DRW_STATE_STENCIL_NEQUAL)
#define DRW_STATE_WRITE_STENCIL_ENABLED \
(DRW_STATE_WRITE_STENCIL | DRW_STATE_WRITE_STENCIL_SHADOW_PASS | \
DRW_STATE_WRITE_STENCIL_SHADOW_FAIL)
namespace draw {
/* -------------------------------------------------------------------- */
/** \name DRWState to GPU state conversion
* \{ */
static inline GPUWriteMask to_write_mask(DRWState state)
{
GPUWriteMask write_mask = GPU_WRITE_NONE;
if (state & DRW_STATE_WRITE_DEPTH) {
write_mask |= GPU_WRITE_DEPTH;
}
if (state & DRW_STATE_WRITE_COLOR) {
write_mask |= GPU_WRITE_COLOR;
}
if (state & DRW_STATE_WRITE_STENCIL_ENABLED) {
write_mask |= GPU_WRITE_STENCIL;
}
return write_mask;
}
static inline GPUFaceCullTest to_face_cull_test(DRWState state)
{
switch (state & (DRW_STATE_CULL_BACK | DRW_STATE_CULL_FRONT)) {
case DRW_STATE_CULL_BACK:
return GPU_CULL_BACK;
case DRW_STATE_CULL_FRONT:
return GPU_CULL_FRONT;
default:
return GPU_CULL_NONE;
}
}
static inline GPUDepthTest to_depth_test(DRWState state)
{
switch (state & DRW_STATE_DEPTH_TEST_ENABLED) {
case DRW_STATE_DEPTH_LESS:
return GPU_DEPTH_LESS;
case DRW_STATE_DEPTH_LESS_EQUAL:
return GPU_DEPTH_LESS_EQUAL;
case DRW_STATE_DEPTH_EQUAL:
return GPU_DEPTH_EQUAL;
case DRW_STATE_DEPTH_GREATER:
return GPU_DEPTH_GREATER;
case DRW_STATE_DEPTH_GREATER_EQUAL:
return GPU_DEPTH_GREATER_EQUAL;
case DRW_STATE_DEPTH_ALWAYS:
return GPU_DEPTH_ALWAYS;
default:
return GPU_DEPTH_NONE;
}
}
static inline GPUStencilOp to_stencil_op(DRWState state)
{
switch (state & DRW_STATE_WRITE_STENCIL_ENABLED) {
case DRW_STATE_WRITE_STENCIL:
return GPU_STENCIL_OP_REPLACE;
case DRW_STATE_WRITE_STENCIL_SHADOW_PASS:
return GPU_STENCIL_OP_COUNT_DEPTH_PASS;
case DRW_STATE_WRITE_STENCIL_SHADOW_FAIL:
return GPU_STENCIL_OP_COUNT_DEPTH_FAIL;
default:
return GPU_STENCIL_OP_NONE;
}
}
static inline GPUStencilTest to_stencil_test(DRWState state)
{
switch (state & DRW_STATE_STENCIL_TEST_ENABLED) {
case DRW_STATE_STENCIL_ALWAYS:
return GPU_STENCIL_ALWAYS;
case DRW_STATE_STENCIL_EQUAL:
return GPU_STENCIL_EQUAL;
case DRW_STATE_STENCIL_NEQUAL:
return GPU_STENCIL_NEQUAL;
default:
return GPU_STENCIL_NONE;
}
}
static inline GPUBlend to_blend(DRWState state)
{
switch (state & DRW_STATE_BLEND_ENABLED) {
case DRW_STATE_BLEND_ADD:
return GPU_BLEND_ADDITIVE;
case DRW_STATE_BLEND_ADD_FULL:
return GPU_BLEND_ADDITIVE_PREMULT;
case DRW_STATE_BLEND_ALPHA:
return GPU_BLEND_ALPHA;
case DRW_STATE_BLEND_ALPHA_PREMUL:
return GPU_BLEND_ALPHA_PREMULT;
case DRW_STATE_BLEND_BACKGROUND:
return GPU_BLEND_BACKGROUND;
case DRW_STATE_BLEND_OIT:
return GPU_BLEND_OIT;
case DRW_STATE_BLEND_MUL:
return GPU_BLEND_MULTIPLY;
case DRW_STATE_BLEND_SUB:
return GPU_BLEND_SUBTRACT;
case DRW_STATE_BLEND_CUSTOM:
return GPU_BLEND_CUSTOM;
case DRW_STATE_LOGIC_INVERT:
return GPU_BLEND_INVERT;
case DRW_STATE_BLEND_ALPHA_UNDER_PREMUL:
return GPU_BLEND_ALPHA_UNDER_PREMUL;
case DRW_STATE_BLEND_TRANSPARENCY:
return GPU_BLEND_TRANSPARENCY;
default:
return GPU_BLEND_NONE;
}
}
static inline GPUProvokingVertex to_provoking_vertex(DRWState state)
{
switch (state & DRW_STATE_FIRST_VERTEX_CONVENTION) {
case DRW_STATE_FIRST_VERTEX_CONVENTION:
return GPU_VERTEX_FIRST;
default:
return GPU_VERTEX_LAST;
}
}
/** \} */
}; // namespace draw
} // namespace blender

View File

@@ -0,0 +1,81 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#define SUBDIV_GROUP_SIZE 64
/* Uniform buffer bindings */
#define SHADER_DATA_BUF_SLOT 0
/* Storage buffer bindings */
#define SUBDIV_FACE_OFFSET_BUF_SLOT 0
#define LINES_INPUT_EDGE_DRAW_FLAG_BUF_SLOT 1
#define LINES_EXTRA_COARSE_FACE_DATA_BUF_SLOT 2
#define LINES_OUTPUT_LINES_BUF_SLOT 3
#define LINES_LINES_LOOSE_FLAGS 4
#define TRIS_EXTRA_COARSE_FACE_DATA_BUF_SLOT 1
#define TRIS_OUTPUT_TRIS_BUF_SLOT 2
#define TRIS_FACE_MAT_OFFSET 3
#define EDGE_FAC_POS_BUF_SLOT 0
#define EDGE_FAC_EDGE_DRAW_FLAG_BUF_SLOT 1
#define EDGE_FAC_POLY_OTHER_MAP_BUF_SLOT 2
#define EDGE_FAC_EDGE_FAC_BUF_SLOT 3
#define PATCH_EVALUATION_SOURCE_VERTEX_BUFFER_BUF_SLOT 0
#define PATCH_EVALUATION_INPUT_PATCH_HANDLES_BUF_SLOT 1
#define PATCH_EVALUATION_QUAD_NODES_BUF_SLOT 2
#define PATCH_EVALUATION_PATCH_COORDS_BUF_SLOT 3
#define PATCH_EVALUATION_INPUT_VERTEX_ORIG_INDEX_BUF_SLOT 4
#define PATCH_EVALUATION_PATCH_ARRAY_BUFFER_BUF_SLOT 5
#define PATCH_EVALUATION_PATCH_INDEX_BUFFER_BUF_SLOT 6
#define PATCH_EVALUATION_PATCH_PARAM_BUFFER_BUF_SLOT 7
#define PATCH_EVALUATION_OUTPUT_FVAR_BUF_SLOT 8
#define PATCH_EVALUATION_OUTPUT_FDOTS_VERTEX_BUFFER_BUF_SLOT 8
#define PATCH_EVALUATION_OUTPUT_NORMALS_BUF_SLOT 9
#define PATCH_EVALUATION_OUTPUT_INDICES_BUF_SLOT 10
#define PATCH_EVALUATION_EXTRA_COARSE_FACE_DATA_BUF_SLOT 11
#define PATCH_EVALUATION_OUTPUT_POS_BUF_SLOT 9
#define PATCH_EVALUATION_SOURCE_EXTRA_VERTEX_BUFFER_BUF_SLOT 10
#define PATCH_EVALUATION_OUTPUT_ORCOS_BUF_SLOT 11
#define CUSTOM_DATA_SOURCE_DATA_BUF_SLOT 1
#define CUSTOM_DATA_FACE_PTEX_OFFSET_BUF_SLOT 2
#define CUSTOM_DATA_PATCH_COORDS_BUF_SLOT 3
#define CUSTOM_DATA_EXTRA_COARSE_FACE_DATA_BUF_SLOT 4
#define CUSTOM_DATA_DESTINATION_DATA_BUF_SLOT 5
#define SCULPT_DATA_SCULPT_MASK_BUF_SLOT 0
#define SCULPT_DATA_SCULPT_FACE_SET_COLOR_BUF_SLOT 1
#define SCULPT_DATA_SCULPT_DATA_BUF_SLOT 2
#define STRETCH_ANGLE_POS_BUF_SLOT 0
#define STRETCH_ANGLE_UVS_BUF_SLOT 1
#define STRETCH_ANGLE_UV_STRETCHES_BUF_SLOT 2
#define STRETCH_AREA_COARSE_STRETCH_AREA_BUF_SLOT 1
#define STRETCH_AREA_SUBDIV_STRETCH_AREA_BUF_SLOT 2
#define NORMALS_ACCUMULATE_POS_BUF_SLOT 0
#define NORMALS_ACCUMULATE_FACE_ADJACENCY_OFFSETS_BUF_SLOT 1
#define NORMALS_ACCUMULATE_FACE_ADJACENCY_LISTS_BUF_SLOT 2
#define NORMALS_ACCUMULATE_VERTEX_LOOP_MAP_BUF_SLOT 3
#define NORMALS_ACCUMULATE_NORMALS_BUF_SLOT 4
#define PAINT_OVERLAY_EXTRA_COARSE_FACE_DATA_BUF_SLOT 1
#define PAINT_OVERLAY_EXTRA_INPUT_VERT_ORIG_INDEX_SLOT 2
#define PAINT_OVERLAY_OUTPUT_FLAG_SLOT 3
#define LOOP_NORMALS_POS_SLOT 1
#define LOOP_NORMALS_EXTRA_COARSE_FACE_DATA_BUF_SLOT 2
#define LOOP_NORMALS_VERT_NORMALS_BUF_SLOT 3
#define LOOP_NORMALS_VERTEX_LOOP_MAP_BUF_SLOT 4
#define LOOP_NORMALS_OUTPUT_LNOR_BUF_SLOT 5

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
#include "draw_subdiv_defines.hh"
#include "GPU_shader_shared_utils.hh"
struct [[host_shared]] DRWSubdivUboStorage {
/* Offsets in the buffers data where the source and destination data start. */
int src_offset;
int dst_offset;
/* Parameters for the DRWPatchMap. */
int min_patch_face;
int max_patch_face;
int max_depth;
bool32_t patches_are_triangular;
/* Coarse topology information. */
int coarse_face_count;
uint edge_loose_offset;
/* Refined topology information. */
uint num_subdiv_loops;
/* The sculpt mask data layer may be null. */
bool32_t has_sculpt_mask;
/* Masks for the extra coarse face data. */
uint coarse_face_select_mask;
uint coarse_face_smooth_mask;
uint coarse_face_active_mask;
uint coarse_face_hidden_mask;
uint coarse_face_loopstart_mask;
/* Number of elements to process in the compute shader (can be the coarse quad count, or the
* final vertex count, depending on which compute pass we do). This is used to early out in case
* of out of bond accesses as compute dispatch are of fixed size. */
uint total_dispatch_size;
bool32_t is_edit_mode;
bool32_t use_hide;
int _pad3;
int _pad4;
};
struct [[host_shared]] SculptData {
uint face_set_color;
float mask;
};
/* Mirror of #UVStretchAngle in the C++ code, but using floats until proper data compression
* is implemented for all subdivision data. */
struct [[host_shared]] UVStretchAngle {
float angle;
float uv_angle0;
float uv_angle1;
};
struct [[host_shared]] Position {
float x;
float y;
float z;
};
struct [[host_shared]] Normal {
float x;
float y;
float z;
};
/* Structure for #CompressedPatchCoord. */
struct [[host_shared]] BlenderPatchCoord {
int patch_index;
uint encoded_uv;
};
/* Patch evaluation - F-dots. */
/* float3 is padded to float4, but the format used for face-dots does not have any padding. */
struct [[host_shared]] FDotVert {
float x;
float y;
float z;
};
/* Same here, do not use float3. */
struct [[host_shared]] FDotNor {
float x;
float y;
float z;
float flag;
};
/* This structure is a carbon copy of OpenSubDiv's #PatchTable::PatchHandle. */
struct [[host_shared]] PatchHandle {
int array_index;
int patch_index;
int vertex_index;
};
/* This structure is a carbon copy of OpenSubDiv's #PatchCoord. */
struct [[host_shared]] PatchCoord {
int array_index;
int patch_index;
int vertex_index;
float u;
float v;
};
/* This structure is a carbon copy of OpenSubDiv's #PatchCoord.QuadNode.
* Each child is a bit-field. */
struct [[host_shared]] QuadNode {
uint4 child;
};

View File

@@ -0,0 +1,280 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_array.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLI_sys_types.h"
#include "mesh_extractors/extract_mesh.hh"
namespace blender {
struct BMesh;
namespace gpu {
class IndexBuf;
class UniformBuf;
class VertBuf;
} // namespace gpu
struct GPUVertFormat;
struct Mesh;
struct Object;
namespace bke::subdiv {
struct Subdiv;
}
struct ToolSettings;
namespace draw {
struct MeshBatchCache;
struct MeshBufferCache;
struct MeshRenderData;
/* -------------------------------------------------------------------- */
/** \name DRWPatchMap
*
* This is a GPU version of the OpenSubDiv PatchMap. The quad tree and the patch handles are copied
* to GPU buffers in order to lookup the right patch for a given set of patch coordinates.
* \{ */
struct DRWPatchMap {
gpu::VertBuf *patch_map_handles;
gpu::VertBuf *patch_map_quadtree;
int min_patch_face;
int max_patch_face;
int max_depth;
bool patches_are_triangular;
};
/** \} */
/* -------------------------------------------------------------------- */
/** \name DRWSubdivCache
*
* This holds the various buffers used to evaluate and render subdivision through OpenGL.
* \{ */
struct DRWSubdivCache {
const Mesh *mesh;
BMesh *bm;
bke::subdiv::Subdiv *subdiv;
bool optimal_display;
bool hide_unmapped_edges;
bool use_custom_loop_normals;
/* Coordinates used to evaluate patches for positions and normals. */
gpu::VertBuf *patch_coords;
/* Coordinates used to evaluate patches for attributes. */
gpu::VertBuf *corner_patch_coords;
/* Coordinates used to evaluate patches for the face centers (or face dots) in edit-mode. */
gpu::VertBuf *fdots_patch_coords;
/* Resolution used to generate the patch coordinates. */
int resolution;
/* Number of subdivided loops, also the number of patch coordinates since we have one coordinate
* but quad corner/vertex. */
uint num_subdiv_loops;
uint num_subdiv_edges;
uint num_subdiv_triangles;
uint num_subdiv_verts;
uint num_subdiv_quads;
/* We only do the subdivision traversal for full faces, however we may have geometries that only
* have loose edges (e.g. a custom bone shape). This flag is used to detect those cases, as the
* counters above will all be set to zero if we do not have subdivision loops. */
bool may_have_loose_geom;
/* Number of faces in the coarse mesh, notably used to compute a coarse face index given a
* subdivision loop index. */
int num_coarse_faces;
/* Maps subdivision loop to subdivided vertex index. */
int *subdiv_loop_subdiv_vert_index;
/* Maps subdivision loop to subdivided edge index. */
int *subdiv_loop_subdiv_edge_index;
/* Maps subdivision loop to original coarse face index. */
int *subdiv_loop_face_index;
/* Indices of faces adjacent to the vertices, ordered by vertex index, with no particular
* winding. */
gpu::VertBuf *subdiv_vert_face_adjacency;
/* The difference between value (i + 1) and (i) gives the number of faces adjacent to vertex (i).
*/
gpu::VertBuf *subdiv_vert_face_adjacency_offsets;
/* Maps subdivision loop to original coarse vertex index, only really useful for edit mode. */
gpu::VertBuf *verts_orig_index;
/* Maps subdivision loop to original coarse edge index, only really useful for edit mode. */
gpu::VertBuf *edges_orig_index;
/* Indicates if edge should be drawn in optimal display mode. */
gpu::VertBuf *edges_draw_flag;
/* Owned by #Subdiv. Indexed by coarse face index, difference between value (i + 1) and (i)
* gives the number of ptex faces for coarse face (i). */
Span<int> face_ptex_offset;
/* Vertex buffer for face_ptex_offset. */
gpu::VertBuf *face_ptex_offset_buffer;
Array<int> subdiv_face_offset;
gpu::VertBuf *subdiv_face_offset_buffer;
/* Contains the start loop index and the smooth flag for each coarse face. */
gpu::VertBuf *extra_coarse_face_data;
/* Material offsets. */
int *mat_start;
int *mat_end;
gpu::VertBuf *face_mat_offset;
DRWPatchMap gpu_patch_map;
/**
* Subdivided vertices of loose edges. The size of this array is the number of loose edges
* multiplied with the resolution. For storage in the VBO the data is duplicated for each edge.
*/
Array<float3> loose_edge_positions;
/* UBO to store settings for the various compute shaders. */
gpu::UniformBuf *ubo;
/* Extra flags, passed to the UBO. */
bool is_edit_mode;
bool use_hide;
};
/* Only frees the data of the cache, caller is responsible to free the cache itself if necessary.
*/
void draw_subdiv_cache_free(DRWSubdivCache &cache);
/** \} */
void DRW_create_subdivision(Object &ob,
Mesh &mesh,
MeshBatchCache &batch_cache,
MeshBufferCache &mbc,
Span<IBOType> ibo_requests,
Span<VBOType> vbo_requests,
bool is_editmode,
bool is_paint_mode,
bool do_final,
bool do_uvedit,
bool do_cage,
const ToolSettings *ts,
bool use_hide);
void DRW_subdivide_loose_geom(DRWSubdivCache &subdiv_cache, const MeshBufferCache &cache);
void DRW_subdiv_cache_free(bke::subdiv::Subdiv *subdiv);
gpu::VertBufPtr draw_subdiv_init_origindex_buffer(int32_t *vert_origindex,
uint num_loops,
uint loose_len);
gpu::VertBuf *draw_subdiv_build_origindex_buffer(int *vert_origindex, uint num_loops);
gpu::VertBufPtr draw_subdiv_init_origindex_buffer(Span<int32_t> vert_origindex, uint loose_len);
gpu::VertBuf *draw_subdiv_build_origindex_buffer(Span<int> vert_origindex);
/* Compute shader functions. */
void draw_subdiv_build_sculpt_data_buffer(const DRWSubdivCache &cache,
gpu::VertBuf *mask_vbo,
gpu::VertBuf *face_set_vbo,
gpu::VertBuf *sculpt_data);
void draw_subdiv_accumulate_normals(const DRWSubdivCache &cache,
gpu::VertBuf *pos,
gpu::VertBuf *face_adjacency_offsets,
gpu::VertBuf *face_adjacency_lists,
gpu::VertBuf *vert_loop_map,
gpu::VertBuf *vert_normals);
void draw_subdiv_extract_pos(const DRWSubdivCache &cache, gpu::VertBuf *pos, gpu::VertBuf *orco);
void draw_subdiv_interp_custom_data(const DRWSubdivCache &cache,
gpu::VertBuf &src_data,
gpu::VertBuf &dst_data,
GPUVertCompType comp_type,
int dimensions,
int dst_offset);
void draw_subdiv_interp_corner_normals(const DRWSubdivCache &cache,
gpu::VertBuf &src_data,
gpu::VertBuf &dst_data);
void draw_subdiv_extract_uvs(const DRWSubdivCache &cache,
gpu::VertBuf *uvs,
int face_varying_channel,
int dst_offset);
void draw_subdiv_build_edge_fac_buffer(const DRWSubdivCache &cache,
gpu::VertBuf *pos,
gpu::VertBuf *edge_draw_flag,
gpu::VertBuf *poly_other_map,
gpu::VertBuf *edge_fac);
void draw_subdiv_build_tris_buffer(const DRWSubdivCache &cache,
gpu::IndexBuf *subdiv_tris,
int material_count);
void draw_subdiv_build_lines_buffer(const DRWSubdivCache &cache, gpu::IndexBuf *lines_indices);
void draw_subdiv_build_lines_loose_buffer(const DRWSubdivCache &cache,
gpu::IndexBuf *lines_indices,
gpu::VertBuf *lines_flags,
uint edge_loose_offset,
uint num_loose_edges);
void draw_subdiv_build_fdots_buffers(const DRWSubdivCache &cache,
gpu::VertBuf *fdots_pos,
gpu::VertBuf *fdots_nor,
gpu::IndexBuf *fdots_indices);
void draw_subdiv_build_lnor_buffer(const DRWSubdivCache &cache,
gpu::VertBuf *pos,
gpu::VertBuf *vert_normals,
gpu::VertBuf *subdiv_corner_verts,
gpu::VertBuf *lnor);
void draw_subdiv_build_paint_overlay_flag_buffer(const DRWSubdivCache &cache, gpu::VertBuf &flags);
void draw_subdiv_build_edituv_stretch_area_buffer(const DRWSubdivCache &cache,
gpu::VertBuf *coarse_data,
gpu::VertBuf *subdiv_data);
void draw_subdiv_build_edituv_stretch_angle_buffer(const DRWSubdivCache &cache,
gpu::VertBuf *pos,
gpu::VertBuf *uvs,
int uvs_offset,
gpu::VertBuf *stretch_angles);
/** For every coarse edge, there are `resolution - 1` subdivided edges. */
inline int subdiv_edges_per_coarse_edge(const DRWSubdivCache &cache)
{
return cache.resolution - 1;
}
/** For every subdivided edge, there are two coarse vertices stored in vertex buffers. */
inline int subdiv_verts_per_coarse_edge(const DRWSubdivCache &cache)
{
return subdiv_edges_per_coarse_edge(cache) * 2;
}
/** The number of subdivided edges from base mesh loose edges. */
inline int subdiv_loose_edges_num(const MeshRenderData &mr, const DRWSubdivCache &cache)
{
return mr.loose_edges.size() * subdiv_edges_per_coarse_edge(cache);
}
/** Size of vertex buffers including all face corners, loose edges, and loose vertices. */
inline int subdiv_full_vbo_size(const MeshRenderData &mr, const DRWSubdivCache &cache)
{
return cache.num_subdiv_loops + subdiv_loose_edges_num(mr, cache) * 2 + mr.loose_verts.size();
}
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,349 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "DRW_render.hh"
#include "GPU_compute.hh"
#include "GPU_debug.hh"
#include "draw_context_private.hh"
#include "draw_shader.hh"
#include "draw_view.hh"
#include "draw_debug.hh"
namespace blender::draw {
std::atomic<uint32_t> View::global_sync_counter_ = 1;
void View::sync(const float4x4 &view_mat, const float4x4 &win_mat, int view_id)
{
data_[view_id].viewmat = view_mat;
data_[view_id].viewinv = math::invert(view_mat);
data_[view_id].winmat = win_mat;
data_[view_id].wininv = math::invert(win_mat);
is_inverted_ = (is_negative_m4(view_mat.ptr()) == is_negative_m4(win_mat.ptr()));
frustum_boundbox_calc(view_id);
frustum_culling_planes_calc(view_id);
frustum_culling_sphere_calc(view_id);
dirty_ = true;
manager_fingerprint_ = 0;
/* Add 2 to always have a non-null number even in case of overflow. */
sync_counter_ = (global_sync_counter_ += 2);
}
void View::frustum_boundbox_calc(int view_id)
{
/* Extract the 8 corners from a Projection Matrix. */
#if 0 /* Equivalent to this but it has accuracy problems. */
std::array<float3, 8> box = bounds::corners(Bounds<float3>(float3 (-1), float3 (1)));
for (int i = 0; i < 8; i++) {
mul_project_m4_v3(data_.wininv.ptr(), box[i]);
}
#endif
MutableSpan<float4> corners = {culling_[view_id].frustum_corners.corners,
int64_t(ARRAY_SIZE(culling_[view_id].frustum_corners.corners))};
float left, right, bottom, top, near, far;
bool is_persp = data_[view_id].winmat[3][3] == 0.0f;
projmat_dimensions(data_[view_id].winmat.ptr(), &left, &right, &bottom, &top, &near, &far);
corners[0][2] = corners[3][2] = corners[7][2] = corners[4][2] = -near;
corners[0][0] = corners[3][0] = left;
corners[4][0] = corners[7][0] = right;
corners[0][1] = corners[4][1] = bottom;
corners[7][1] = corners[3][1] = top;
/* Get the coordinates of the far plane. */
if (is_persp) {
float sca_far = far / near;
left *= sca_far;
right *= sca_far;
bottom *= sca_far;
top *= sca_far;
}
corners[1][2] = corners[2][2] = corners[6][2] = corners[5][2] = -far;
corners[1][0] = corners[2][0] = left;
corners[6][0] = corners[5][0] = right;
corners[1][1] = corners[5][1] = bottom;
corners[2][1] = corners[6][1] = top;
const float4x4 &view_inv = data_[view_id].viewinv;
/* Transform into world space. */
for (float4 &corner : corners) {
corner = float4(math::transform_point(view_inv, float3(corner)), 1.0);
}
}
void View::frustum_culling_planes_calc(int view_id)
{
float4x4 persmat = data_[view_id].winmat * data_[view_id].viewmat;
planes_from_projmat(persmat.ptr(),
culling_[view_id].frustum_planes.planes[0],
culling_[view_id].frustum_planes.planes[5],
culling_[view_id].frustum_planes.planes[1],
culling_[view_id].frustum_planes.planes[3],
culling_[view_id].frustum_planes.planes[4],
culling_[view_id].frustum_planes.planes[2]);
/* Normalize. */
for (float4 &plane : culling_[view_id].frustum_planes.planes) {
float len = math::length(plane.xyz());
if (len != 0.0f) {
plane /= len;
}
}
}
void View::frustum_culling_sphere_calc(int view_id)
{
BoundSphere &bsphere = *reinterpret_cast<BoundSphere *>(&culling_[view_id].bound_sphere);
Span<float4> corners = {culling_[view_id].frustum_corners.corners,
int64_t(ARRAY_SIZE(culling_[view_id].frustum_corners.corners))};
/* Extract Bounding Sphere */
if (data_[view_id].winmat[3][3] != 0.0f) {
/* Orthographic */
/* The most extreme points on the near and far plane. (normalized device coords). */
const float *nearpoint = corners[0];
const float *farpoint = corners[6];
/* just use median point */
mid_v3_v3v3(bsphere.center, farpoint, nearpoint);
bsphere.radius = len_v3v3(bsphere.center, farpoint);
}
else if (data_[view_id].winmat[2][0] == 0.0f && data_[view_id].winmat[2][1] == 0.0f) {
/* Perspective with symmetrical frustum. */
/* We obtain the center and radius of the circumscribed circle of the
* isosceles trapezoid composed by the diagonals of the near and far clipping plane */
/* center of each clipping plane */
float mid_min[3], mid_max[3];
mid_v3_v3v3(mid_min, corners[3], corners[4]);
mid_v3_v3v3(mid_max, corners[2], corners[5]);
/* square length of the diagonals of each clipping plane */
float a_sq = len_squared_v3v3(corners[3], corners[4]);
float b_sq = len_squared_v3v3(corners[2], corners[5]);
/* distance squared between clipping planes */
float h_sq = len_squared_v3v3(mid_min, mid_max);
float fac = (4 * h_sq + b_sq - a_sq) / (8 * h_sq);
/* The goal is to get the smallest sphere,
* not the sphere that passes through each corner */
CLAMP(fac, 0.0f, 1.0f);
interp_v3_v3v3(bsphere.center, mid_min, mid_max, fac);
/* distance from the center to one of the points of the far plane (1, 2, 5, 6) */
bsphere.radius = len_v3v3(bsphere.center, corners[1]);
}
else {
/* Perspective with asymmetrical frustum. */
/* We put the sphere center on the line that goes from origin
* to the center of the far clipping plane. */
/* Detect which of the corner of the far clipping plane is the farthest to the origin */
float nfar[4]; /* most extreme far point in NDC space */
float farxy[2]; /* far-point projection onto the near plane */
float farpoint[3] = {0.0f}; /* most extreme far point in camera coordinate */
float nearpoint[3]; /* most extreme near point in camera coordinate */
float farcenter[3] = {0.0f}; /* center of far clipping plane in camera coordinate */
float F = -1.0f, N; /* square distance of far and near point to origin */
float f, n; /* distance of far and near point to z axis. f is always > 0 but n can be < 0 */
float e, s; /* far and near clipping distance (<0) */
float c; /* slope of center line = distance of far clipping center
* to z axis / far clipping distance. */
float z; /* projection of sphere center on z axis (<0) */
/* Find farthest corner and center of far clip plane. */
float corner[3] = {1.0f, 1.0f, 1.0f}; /* in clip space */
for (int i = 0; i < 4; i++) {
float point[3];
mul_v3_project_m4_v3(point, data_[view_id].wininv.ptr(), corner);
float len = len_squared_v3(point);
if (len > F) {
copy_v3_v3(nfar, corner);
copy_v3_v3(farpoint, point);
F = len;
}
add_v3_v3(farcenter, point);
/* rotate by 90 degree to walk through the 4 points of the far clip plane */
float tmp = corner[0];
corner[0] = -corner[1];
corner[1] = tmp;
}
/* the far center is the average of the far clipping points */
mul_v3_fl(farcenter, 0.25f);
/* the extreme near point is the opposite point on the near clipping plane */
copy_v3_fl3(nfar, -nfar[0], -nfar[1], -1.0f);
mul_v3_project_m4_v3(nearpoint, data_[view_id].wininv.ptr(), nfar);
/* this is a frustum projection */
N = len_squared_v3(nearpoint);
e = farpoint[2];
s = nearpoint[2];
/* distance to view Z axis */
f = len_v2(farpoint);
/* get corresponding point on the near plane */
mul_v2_v2fl(farxy, farpoint, s / e);
/* this formula preserve the sign of n */
sub_v2_v2(nearpoint, farxy);
n = f * s / e - len_v2(nearpoint);
c = len_v2(farcenter) / e;
/* the big formula, it simplifies to (F-N)/(2(e-s)) for the symmetric case */
z = (F - N) / (2.0f * (e - s + c * (f - n)));
bsphere.center[0] = farcenter[0] * z / e;
bsphere.center[1] = farcenter[1] * z / e;
bsphere.center[2] = z;
/* For XR, the view matrix may contain a scale factor. Then, transforming only the center
* into world space after calculating the radius will result in incorrect behavior. */
mul_m4_v3(data_[view_id].viewinv.ptr(), bsphere.center); /* Transform to world space. */
mul_m4_v3(data_[view_id].viewinv.ptr(), farpoint);
bsphere.radius = len_v3v3(bsphere.center, farpoint);
}
}
void View::bind()
{
if (dirty_ && !procedural_) {
dirty_ = false;
data_.push_update();
culling_.push_update();
}
GPU_uniformbuf_bind(data_, DRW_VIEW_UBO_SLOT);
GPU_uniformbuf_bind(culling_, DRW_VIEW_CULLING_UBO_SLOT);
}
void View::compute_procedural_bounds()
{
/* Sync happens on the GPU. This is called after each sync. */
manager_fingerprint_ = 0;
/* Add 2 to always have a non-null number even in case of overflow. */
sync_counter_ = (global_sync_counter_ += 2);
GPU_debug_group_begin("View.compute_procedural_bounds");
gpu::Shader *shader = DRW_shader_draw_view_finalize_get();
GPU_shader_bind(shader);
GPU_uniformbuf_bind_as_ssbo(culling_, GPU_shader_get_ssbo_binding(shader, "view_culling_buf"));
GPU_uniformbuf_bind(data_, DRW_VIEW_UBO_SLOT);
GPU_compute_dispatch(shader, 1, 1, 1);
GPU_memory_barrier(GPU_BARRIER_UNIFORM);
GPU_debug_group_end();
}
void View::compute_visibility(ObjectBoundsBuf &bounds,
ObjectInfosBuf & /*infos*/,
uint resource_len,
bool debug_freeze)
{
if (debug_freeze && frozen_ == false) {
data_freeze_[0] = static_cast<ViewMatrices>(data_[0]);
data_freeze_.push_update();
culling_freeze_[0] = static_cast<ViewCullingData>(culling_[0]);
culling_freeze_.push_update();
}
if (debug_freeze) {
float4x4 persmat = data_freeze_[0].winmat * data_freeze_[0].viewmat;
drw_debug_matrix_as_bbox(math::invert(persmat), float4(0, 1, 0, 1));
}
frozen_ = debug_freeze;
GPU_debug_group_begin("View.compute_visibility");
/* TODO(fclem): Early out if visibility hasn't changed. */
uint word_per_draw = this->visibility_word_per_draw();
/* Switch between tightly packed and set of whole word per instance. */
uint words_len = (view_len_ == 1) ? divide_ceil_u(resource_len, 32) :
resource_len * word_per_draw;
words_len = ceil_to_multiple_u(max_ii(1, words_len), 4);
/* TODO(fclem): Resize to nearest pow2 to reduce fragmentation. */
visibility_buf_.resize(words_len);
const uint32_t data = 0xFFFFFFFFu;
GPU_storagebuf_clear(visibility_buf_, data);
if (do_visibility_) {
gpu::Shader *shader = DRW_shader_draw_visibility_compute_get();
GPU_shader_bind(shader);
GPU_shader_uniform_1i(shader, "resource_len", resource_len);
GPU_shader_uniform_1i(shader, "view_len", view_len_);
GPU_shader_uniform_1i(shader, "visibility_word_per_draw", word_per_draw);
GPU_storagebuf_bind(bounds, GPU_shader_get_ssbo_binding(shader, "bounds_buf"));
GPU_storagebuf_bind(visibility_buf_, GPU_shader_get_ssbo_binding(shader, "visibility_buf"));
GPU_uniformbuf_bind(frozen_ ? data_freeze_ : data_, DRW_VIEW_UBO_SLOT);
GPU_uniformbuf_bind(frozen_ ? culling_freeze_ : culling_, DRW_VIEW_CULLING_UBO_SLOT);
GPU_compute_dispatch(shader, divide_ceil_u(resource_len, DRW_VISIBILITY_GROUP_SIZE), 1, 1);
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
}
if (frozen_) {
/* Bind back the non frozen data. */
GPU_uniformbuf_bind(data_, DRW_VIEW_UBO_SLOT);
GPU_uniformbuf_bind(culling_, DRW_VIEW_CULLING_UBO_SLOT);
}
GPU_debug_group_end();
}
VisibilityBuf &View::get_visibility_buffer()
{
return visibility_buf_;
}
draw::View &View::default_get()
{
return *drw_get().data->default_view;
}
void View::default_set(const float4x4 &view_mat, const float4x4 &win_mat)
{
drw_get().data->default_view->sync(view_mat, win_mat);
}
std::array<float4, 6> View::frustum_planes_get(int view_id) const
{
return {culling_[view_id].frustum_planes.planes[0],
culling_[view_id].frustum_planes.planes[1],
culling_[view_id].frustum_planes.planes[2],
culling_[view_id].frustum_planes.planes[3],
culling_[view_id].frustum_planes.planes[4],
culling_[view_id].frustum_planes.planes[5]};
}
std::array<float3, 8> View::frustum_corners_get(int view_id) const
{
return {culling_[view_id].frustum_corners.corners[0].xyz(),
culling_[view_id].frustum_corners.corners[1].xyz(),
culling_[view_id].frustum_corners.corners[2].xyz(),
culling_[view_id].frustum_corners.corners[3].xyz(),
culling_[view_id].frustum_corners.corners[4].xyz(),
culling_[view_id].frustum_corners.corners[5].xyz(),
culling_[view_id].frustum_corners.corners[6].xyz(),
culling_[view_id].frustum_corners.corners[7].xyz()};
}
} // namespace blender::draw

View File

@@ -0,0 +1,264 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup draw
*
* View description and states.
*
* A `draw::View` object is required for drawing geometry using the DRW API and its internal
* culling system.
*
* One `View` object can actually contain multiple view matrices if the template parameter
* `view_len` is greater than 1. This is called multi-view rendering and the vertex shader must
* setting `drw_view_id` accordingly.
*/
#include "DNA_view3d_types.h"
#include "DRW_gpu_wrapper.hh"
#include "GPU_matrix.hh"
#include "draw_shader_shared.hh"
#include <atomic>
#include <cstdint>
namespace blender::draw {
class Manager;
/* TODO: de-duplicate. */
using ObjectBoundsBuf = StorageArrayBuffer<ObjectBounds, 128>;
using ObjectInfosBuf = StorageArrayBuffer<ObjectInfos, 128>;
using VisibilityBuf = StorageArrayBuffer<uint, 4, true>;
class View {
friend Manager;
/** Number of sync done by views. Used for fingerprint. */
static std::atomic<uint32_t> global_sync_counter_;
/* Local sync counter. Used for fingerprint. */
uint32_t sync_counter_ = 0;
protected:
/** TODO(fclem): Maybe try to reduce the minimum cost if the number of view is lower. */
UniformArrayBuffer<ViewMatrices, DRW_VIEW_MAX> data_;
UniformArrayBuffer<ViewCullingData, DRW_VIEW_MAX> culling_;
/** Frozen version of data_ used for debugging culling. */
UniformArrayBuffer<ViewMatrices, DRW_VIEW_MAX> data_freeze_;
UniformArrayBuffer<ViewCullingData, DRW_VIEW_MAX> culling_freeze_;
/** Result of the visibility computation. 1 bit or 1 or 2 word per resource ID per view. */
VisibilityBuf visibility_buf_;
/* Fingerprint of the manager state when visibility was computed. */
uint64_t manager_fingerprint_ = 0;
const char *debug_name_;
int view_len_ = 0;
bool is_inverted_ = false;
bool do_visibility_ = true;
bool dirty_ = true;
bool frozen_ = false;
bool procedural_ = false;
public:
View(const char *name, int view_len = 1, bool procedural = false)
: visibility_buf_(name), debug_name_(name), view_len_(view_len), procedural_(procedural)
{
BLI_assert(view_len <= DRW_VIEW_MAX);
}
virtual ~View() = default;
void sync(const float4x4 &view_mat, const float4x4 &win_mat, int view_id = 0);
/** Enable or disable every visibility test (frustum culling, HiZ culling). */
void visibility_test(bool enable)
{
do_visibility_ = enable;
}
/**
* Update culling data using a compute shader.
* This is to be used if the matrices were updated externally
* on the GPU (not using the `sync()` method).
*/
void compute_procedural_bounds();
bool is_persp(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].winmat[3][3] == 0.0f;
}
bool is_inverted(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
UNUSED_VARS_NDEBUG(view_id);
return is_inverted_;
}
float far_clip(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
if (is_persp(view_id)) {
return -data_[view_id].winmat[3][2] / (data_[view_id].winmat[2][2] + 1.0f);
}
return -(data_[view_id].winmat[3][2] - 1.0f) / data_[view_id].winmat[2][2];
}
float near_clip(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
if (is_persp(view_id)) {
return -data_[view_id].winmat[3][2] / (data_[view_id].winmat[2][2] - 1.0f);
}
return -(data_[view_id].winmat[3][2] + 1.0f) / data_[view_id].winmat[2][2];
}
const float3 &location(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].viewinv.location();
}
const float3 &forward(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].viewinv.z_axis();
}
const float4x4 &viewmat(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].viewmat;
}
const float4x4 &viewinv(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].viewinv;
}
const float4x4 &winmat(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].winmat;
}
const float4x4 &wininv(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].wininv;
}
/* Compute and return the perspective matrix. */
const float4x4 persmat(int view_id = 0) const
{
BLI_assert(view_id < view_len_);
return data_[view_id].winmat * data_[view_id].viewmat;
}
int visibility_word_per_draw() const
{
return (view_len_ == 1) ? 0 : divide_ceil_u(view_len_, 32);
}
UniformArrayBuffer<ViewMatrices, DRW_VIEW_MAX> &matrices_ubo_get()
{
return data_;
}
/* TODO(fclem): Remove. Global DST access. */
static View &default_get();
static void default_set(const float4x4 &view_mat, const float4x4 &win_mat);
/* Data to save per overlay to not rely on rv3d for rendering.
* TODO(fclem): Compute offset directly from the view. */
struct OffsetData {
/* Copy of rv3d->dist. */
float dist = 0.0f;
/* Copy of rv3d->persp. */
char persp = 0;
/* Copy of rv3d->is_persp. */
bool is_persp = false;
OffsetData() = default;
OffsetData(const RegionView3D &rv3d)
: dist(rv3d.dist), persp(rv3d.persp), is_persp(rv3d.is_persp != 0)
{
}
float4x4 winmat_polygon_offset(const float4x4 &winmat, float offset)
{
float view_dist = dist;
/* Special exception for orthographic camera:
* `view_dist` isn't used as the depth range isn't the same. */
if (persp == RV3D_CAMOB && is_persp == false) {
view_dist = 1.0f / max_ff(fabsf(winmat[0][0]), fabsf(winmat[1][1]));
}
float4x4 result = winmat;
result[3][2] -= GPU_polygon_offset_calc(winmat.ptr(), view_dist, offset);
return result;
}
/* Return unit offset to apply to `gl_Position.z`. To be scaled depending on purpose. */
float polygon_offset_factor(const float4x4 &winmat)
{
float view_dist = dist;
/* Special exception for orthographic camera:
* `view_dist` isn't used as the depth range isn't the same. */
if (persp == RV3D_CAMOB && is_persp == false) {
view_dist = 1.0f / max_ff(fabsf(winmat[0][0]), fabsf(winmat[1][1]));
}
return GPU_polygon_offset_calc(winmat.ptr(), view_dist, 1.0);
}
};
/* Returns frustum planes equations. Available only after sync. */
std::array<float4, 6> frustum_planes_get(int view_id = 0) const;
/* Returns frustum corners positions in world space. Available only after sync.
* Follow bounding box corner order. */
std::array<float3, 8> frustum_corners_get(int view_id = 0) const;
protected:
/** Called from draw manager. */
void bind();
virtual void compute_visibility(ObjectBoundsBuf &bounds,
ObjectInfosBuf &infos,
uint resource_len,
bool debug_freeze);
virtual VisibilityBuf &get_visibility_buffer();
bool has_computed_visibility() const
{
/* NOTE: Even though manager fingerprint is not enough to check for update, it is still
* guaranteed to not be 0. So we can check weather or not this view has computed visibility
* after sync. Asserts will catch invalid usage . */
return manager_fingerprint_ != 0;
}
/* Fingerprint of the view for the current state.
* Not reliable enough for general update detection. Only to be used for debugging assertion. */
uint64_t fingerprint_get() const
{
BLI_assert_msg(sync_counter_ != 0, "View should be synced at least once before use");
return sync_counter_;
}
void update_viewport_size();
/* WARNING: These 3 functions must be called in order */
void frustum_boundbox_calc(int view_id);
void frustum_culling_planes_calc(int view_id);
void frustum_culling_sphere_calc(int view_id);
};
} // namespace blender::draw

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* Contains dynamic drawing using immediate mode
*/
#include "DNA_screen_types.h"
#include "ED_screen.hh"
#include "ED_view3d.hh"
#include "GPU_debug.hh"
#include "GPU_state.hh"
#include "UI_view2d.hh"
#include "WM_types.hh"
#include "BKE_paint.hh"
#include "BKE_screen.hh"
#include "DRW_engine.hh"
#include "DRW_render.hh"
#include "draw_view_c.hh"
#include "view3d_intern.hh"
namespace blender {
/* ******************** region info ***************** */
void DRW_draw_region_info(const bContext *C, ARegion *region)
{
GPU_debug_group_begin("RegionInfo");
view3d_draw_region_info(C, region);
GPU_debug_group_end();
}
/* **************************** 3D Gizmo ******************************** */
void DRW_draw_gizmo_3d(const bContext *C, ARegion *region)
{
/* draw depth culled gizmos - gizmos need to be updated *after* view matrix was set up */
/* TODO: depth culling gizmos is not yet supported, just drawing _3D here, should
* later become _IN_SCENE (and draw _3D separate) */
WM_gizmomap_draw(region->runtime->gizmo_map, C, WM_GIZMOMAP_DRAWSTEP_3D);
}
void DRW_draw_gizmo_2d(const bContext *C, ARegion *region)
{
WM_gizmomap_draw(region->runtime->gizmo_map, C, WM_GIZMOMAP_DRAWSTEP_2D);
GPU_depth_mask(true);
}
} // namespace blender

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#pragma once
namespace blender {
struct bContext;
struct ARegion;
void DRW_draw_region_info(const bContext *C, ARegion *region);
void DRW_draw_gizmo_3d(const bContext *C, ARegion *region);
void DRW_draw_gizmo_2d(const bContext *C, ARegion *region);
} // namespace blender

View File

@@ -0,0 +1,138 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include <memory>
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "GPU_viewport.hh"
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "draw_manager_text.hh"
#include "draw_context_private.hh"
#include "draw_manager.hh"
#include "draw_view_data.hh"
namespace blender {
DRWViewData::DRWViewData()
{
manager = new draw::Manager();
};
DRWViewData::~DRWViewData()
{
this->clear(true);
delete manager;
};
void DRW_view_data_default_lists_from_viewport(DRWViewData *view_data, GPUViewport *viewport)
{
int active_view = GPU_viewport_active_view_get(viewport);
view_data->from_viewport = true;
DefaultFramebufferList *dfbl = &view_data->dfbl;
DefaultTextureList *dtxl = &view_data->dtxl;
/* Depth texture is shared between both stereo views. */
dtxl->depth = GPU_viewport_depth_texture(viewport);
dtxl->color = GPU_viewport_color_texture(viewport, active_view);
dtxl->color_overlay = GPU_viewport_overlay_texture(viewport, active_view);
GPU_framebuffer_ensure_config(&dfbl->default_fb,
{
GPU_ATTACHMENT_TEXTURE(dtxl->depth),
GPU_ATTACHMENT_TEXTURE(dtxl->color),
});
GPU_framebuffer_ensure_config(&dfbl->overlay_fb,
{
GPU_ATTACHMENT_TEXTURE(dtxl->depth),
GPU_ATTACHMENT_TEXTURE(dtxl->color_overlay),
});
GPU_framebuffer_ensure_config(&dfbl->depth_only_fb,
{
GPU_ATTACHMENT_TEXTURE(dtxl->depth),
GPU_ATTACHMENT_NONE,
});
GPU_framebuffer_ensure_config(&dfbl->color_only_fb,
{
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(dtxl->color),
});
GPU_framebuffer_ensure_config(&dfbl->overlay_only_fb,
{
GPU_ATTACHMENT_NONE,
GPU_ATTACHMENT_TEXTURE(dtxl->color_overlay),
});
}
void DRWViewData::clear(bool free_instance_data)
{
GPU_FRAMEBUFFER_FREE_SAFE(this->dfbl.default_fb);
GPU_FRAMEBUFFER_FREE_SAFE(this->dfbl.overlay_fb);
GPU_FRAMEBUFFER_FREE_SAFE(this->dfbl.in_front_fb);
GPU_FRAMEBUFFER_FREE_SAFE(this->dfbl.color_only_fb);
GPU_FRAMEBUFFER_FREE_SAFE(this->dfbl.depth_only_fb);
GPU_FRAMEBUFFER_FREE_SAFE(this->dfbl.overlay_only_fb);
if (!this->from_viewport) {
GPU_TEXTURE_FREE_SAFE(this->dtxl.color);
GPU_TEXTURE_FREE_SAFE(this->dtxl.color_overlay);
GPU_TEXTURE_FREE_SAFE(this->dtxl.depth);
}
GPU_TEXTURE_FREE_SAFE(this->dtxl.depth_in_front);
if (free_instance_data) {
foreach_engine([&](DrawEngine::Pointer &ptr) {
if (ptr.instance) {
ptr.free_instance();
}
});
}
}
void DRWViewData::texture_list_size_validate(const int2 &size)
{
if (this->texture_list_size != size) {
this->clear(false);
copy_v2_v2_int(this->texture_list_size, size);
}
}
void DRW_view_data_reset(DRWViewData *view_data)
{
view_data->foreach_enabled_engine([&](DrawEngine &instance) { instance.used = false; });
for (std::unique_ptr<draw::TextureFromPool> &texture :
view_data->viewport_compositor_passes.values())
{
texture->release();
}
view_data->viewport_compositor_passes.clear();
}
void DRW_view_data_free_unused(DRWViewData *view_data)
{
view_data->foreach_engine([&](DrawEngine::Pointer &ptr) {
if (ptr.instance && ptr.instance->used == false) {
ptr.free_instance();
}
});
}
draw::Manager *DRW_manager_get()
{
BLI_assert(drw_get().view_data_active->manager);
return drw_get().view_data_active->manager;
}
} // namespace blender

View File

@@ -0,0 +1,146 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* Engine data
* Structure containing each draw engine instance data.
*/
#pragma once
#include "BLI_map.hh"
#include "BLI_math_vector_types.hh"
#include "DRW_render.hh"
#include "draw_context_private.hh"
#include "engines/compositor/compositor_engine.h"
#include "engines/eevee/eevee_engine.h"
#include "engines/external/external_engine.h"
#include "engines/gpencil/gpencil_engine.hh"
#include "engines/image/image_engine.h"
#include "engines/overlay/overlay_engine.h"
#include "engines/select/select_engine.hh"
#include "engines/workbench/workbench_engine.h"
namespace blender {
#define GPU_INFO_SIZE 512 /* IMA_MAX_RENDER_TEXT_SIZE */
namespace draw {
class TextureFromPool;
class Manager;
} // namespace draw
struct DRWTextStore;
namespace gpu {
class FrameBuffer;
class Texture;
} // namespace gpu
struct GPUViewport;
/** Buffer and textures used by the viewport by default. */
struct DefaultFramebufferList {
gpu::FrameBuffer *default_fb;
gpu::FrameBuffer *overlay_fb;
gpu::FrameBuffer *in_front_fb;
gpu::FrameBuffer *color_only_fb;
gpu::FrameBuffer *depth_only_fb;
gpu::FrameBuffer *overlay_only_fb;
};
struct DefaultTextureList {
gpu::Texture *color;
gpu::Texture *color_overlay;
gpu::Texture *depth;
gpu::Texture *depth_in_front;
};
struct DRWViewData {
public:
DefaultFramebufferList dfbl = {};
DefaultTextureList dtxl = {};
/** True indicates the textures inside dtxl are from the viewport and should not be freed. */
bool from_viewport = false;
/** Common size for texture in the engines texture list.
* We free all texture lists if it changes. */
int2 texture_list_size = {0, 0};
/** Engines running for this viewport. nullptr if not enabled. */
eevee::Engine eevee;
workbench::Engine workbench;
draw::external::Engine external;
image_engine::Engine image;
draw::gpencil::Engine grease_pencil;
draw::overlay::Engine overlay;
draw::select::Engine object_select;
draw::edit_select::Engine edit_select;
#ifdef WITH_DRAW_DEBUG
draw::edit_select_debug::Engine edit_select_debug;
#endif
draw::compositor_engine::Engine compositor;
/**
* Stores passes needed by the viewport compositor. Engines are expected to populate those in
* every redraw using calls to the #DRW_viewport_pass_texture_get function. The compositor can
* then call the same function to retrieve the passes it needs, which are expected to be
* initialized. Those textures are release when view data is reset.
*/
Map<std::string, std::unique_ptr<draw::TextureFromPool>> viewport_compositor_passes;
/** New per view/viewport manager. Null if not supported by current hardware. */
draw::Manager *manager = nullptr;
public:
DRWViewData();
~DRWViewData();
void texture_list_size_validate(const int2 &size);
template<typename CallbackT> void foreach_engine(CallbackT callback)
{
/* IMPORTANT: Order here defines the draw order. */
/* Render engines. Output to the render result frame-buffer. Mutually exclusive. */
callback(eevee);
callback(workbench);
callback(external);
callback(image);
#ifdef WITH_DRAW_DEBUG
callback(edit_select_debug);
#endif
/** Grease pencil. Merge its output to the render result frame-buffer. */
callback(grease_pencil);
/** GPU compositor. Processes render result and output to the render result frame-buffer. */
callback(compositor);
/** Overlays. Draw on a separate overlay frame-buffer. Can read render result. */
callback(overlay);
/** Selection. Are always enabled alone and have no interaction with other engines. */
callback(object_select);
callback(edit_select);
}
template<typename CallbackT> void foreach_enabled_engine(CallbackT callback)
{
foreach_engine([&](DrawEngine::Pointer &ptr) {
if (ptr.instance == nullptr || ptr.instance->used == false) {
return;
}
callback(*ptr.instance);
});
}
private:
void clear(bool free_instance_data);
};
void DRW_view_data_default_lists_from_viewport(DRWViewData *view_data, GPUViewport *viewport);
void DRW_view_data_reset(DRWViewData *view_data);
void DRW_view_data_free_unused(DRWViewData *view_data);
} // namespace blender

View File

@@ -0,0 +1,297 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Contains Volume object GPU attributes configuration.
*/
#include "DRW_gpu_wrapper.hh"
#include "DRW_render.hh"
#include "DNA_fluid_types.h"
#include "DNA_volume_types.h"
#include "BLI_string.h"
#include "BKE_global.hh"
#include "BKE_mesh.hh"
#include "BKE_modifier.hh"
#include "BKE_volume.hh"
#include "BKE_volume_render.hh"
#include "GPU_material.hh"
#include "draw_cache.hh"
#include "draw_common_c.hh"
#include "draw_context_private.hh"
#include "draw_common.hh"
namespace blender::draw {
using VolumeInfosBuf = draw::UniformBuffer<VolumeInfos>;
struct VolumeUniformBufPool {
Vector<VolumeInfosBuf *> ubos;
uint used = 0;
~VolumeUniformBufPool()
{
for (VolumeInfosBuf *ubo : ubos) {
delete ubo;
}
}
void reset()
{
used = 0;
}
VolumeInfosBuf *alloc()
{
if (used >= ubos.size()) {
VolumeInfosBuf *buf = new VolumeInfosBuf();
ubos.append(buf);
}
return ubos[used++];
}
};
struct VolumeModule {
VolumeUniformBufPool ubo_pool;
draw::Texture dummy_zero;
draw::Texture dummy_one;
VolumeModule()
{
const float zero[4] = {0.0f, 0.0f, 0.0f, 0.0f};
const float one[4] = {1.0f, 1.0f, 1.0f, 1.0f};
const eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ;
dummy_zero.ensure_3d(gpu::TextureFormat::SFLOAT_32_32_32_32, int3(1), usage, zero);
dummy_one.ensure_3d(gpu::TextureFormat::SFLOAT_32_32_32_32, int3(1), usage, one);
GPU_texture_extend_mode(dummy_zero, GPU_SAMPLER_EXTEND_MODE_REPEAT);
GPU_texture_extend_mode(dummy_one, GPU_SAMPLER_EXTEND_MODE_REPEAT);
}
gpu::Texture *grid_default_texture(GPUDefaultValue default_value)
{
switch (default_value) {
case GPU_DEFAULT_0:
return dummy_zero;
case GPU_DEFAULT_1:
return dummy_one;
}
return dummy_zero;
}
};
void DRW_volume_init(DRWData *drw_data)
{
if (drw_data == nullptr) {
drw_data = drw_get().data;
}
if (drw_data->volume_module == nullptr) {
drw_data->volume_module = MEM_new<VolumeModule>("VolumeModule");
}
drw_data->volume_module->ubo_pool.reset();
}
void DRW_volume_module_free(draw::VolumeModule *module)
{
MEM_delete(module);
}
/* -------------------------------------------------------------------- */
/** \name Public API for render engines.
* \{ */
template<typename PassType>
PassType *volume_world_grids_init(PassType &ps, ListBaseWrapper<GPUMaterialAttribute> &attrs)
{
VolumeModule &module = *drw_get().data->volume_module;
PassType *sub = &ps.sub("World Volume");
for (const GPUMaterialAttribute *attr : attrs) {
sub->bind_texture(attr->input_name, module.grid_default_texture(attr->default_value));
}
return sub;
}
template<typename PassType>
PassType *volume_object_grids_init(PassType &ps,
Object *ob,
ListBaseWrapper<GPUMaterialAttribute> &attrs)
{
Volume &volume = DRW_object_get_data_for_drawing<Volume>(*ob);
BKE_volume_load(&volume, G.main);
/* Render nothing if there is no attribute. */
if (BKE_volume_num_grids(&volume) == 0) {
return nullptr;
}
VolumeModule &module = *drw_get().data->volume_module;
VolumeInfosBuf &volume_infos = *module.ubo_pool.alloc();
volume_infos.density_scale = BKE_volume_density_scale(&volume, ob->object_to_world().ptr());
volume_infos.color_mul = float4(1.0f);
volume_infos.temperature_mul = 1.0f;
volume_infos.temperature_bias = 0.0f;
PassType *sub = &ps.sub("Volume Object SubPass");
/* Bind volume grid textures. */
int grid_id = 0;
for (const GPUMaterialAttribute *attr : attrs) {
const bke::VolumeGridData *volume_grid = BKE_volume_grid_find(&volume, attr->name);
const DRWVolumeGrid *drw_grid = (volume_grid) ?
DRW_volume_batch_cache_get_grid(&volume, volume_grid) :
nullptr;
/* Handle 3 cases here:
* - Grid exists and texture was loaded -> use texture.
* - Grid exists but has zero size or failed to load -> use zero.
* - Grid does not exist -> use default value. */
const gpu::Texture *grid_tex = (drw_grid) ? drw_grid->texture :
(volume_grid) ?
module.dummy_zero :
module.grid_default_texture(attr->default_value);
/* TODO(@pragma37): bind_texture const support ? */
sub->bind_texture(attr->input_name, const_cast<gpu::Texture *>(grid_tex));
volume_infos.grids_xform[grid_id++] = drw_grid ? float4x4(drw_grid->object_to_texture) :
float4x4::identity();
}
volume_infos.push_update();
sub->bind_ubo("drw_volume", volume_infos);
return sub;
}
template<typename PassType>
PassType *drw_volume_object_mesh_init(PassType &ps,
Scene *scene,
Object *ob,
ListBaseWrapper<GPUMaterialAttribute> &attrs)
{
VolumeModule &module = *drw_get().data->volume_module;
VolumeInfosBuf &volume_infos = *module.ubo_pool.alloc();
ModifierData *md = nullptr;
volume_infos.density_scale = 1.0f;
volume_infos.color_mul = float4(1.0f);
volume_infos.temperature_mul = 1.0f;
volume_infos.temperature_bias = 0.0f;
bool has_fluid_modifier = (md = BKE_modifiers_findby_type(ob, eModifierType_Fluid)) &&
BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime) &&
(reinterpret_cast<FluidModifierData *>(md))->domain != nullptr;
FluidModifierData *fmd = has_fluid_modifier ? reinterpret_cast<FluidModifierData *>(md) :
nullptr;
FluidDomainSettings *fds = has_fluid_modifier ? fmd->domain : nullptr;
PassType *sub = nullptr;
if (!has_fluid_modifier || (fds->type != FLUID_DOMAIN_TYPE_GAS)) {
/* No volume attributes or fluid domain. */
sub = &ps.sub("Volume Mesh SubPass");
int grid_id = 0;
for (const GPUMaterialAttribute *attr : attrs) {
sub->bind_texture(attr->input_name, module.grid_default_texture(attr->default_value));
volume_infos.grids_xform[grid_id++] = float4x4::identity();
}
}
else if (fds->fluid) {
/* Smoke Simulation. */
DRW_smoke_ensure(fmd, fds->flags & FLUID_DOMAIN_USE_NOISE);
sub = &ps.sub("Volume Modifier SubPass");
float3 location, scale;
BKE_mesh_texspace_get(&DRW_object_get_data_for_drawing<Mesh>(*ob), location, scale);
float3 orco_mul = math::safe_rcp(scale * 2.0);
float3 orco_add = (location - scale) * -orco_mul;
/* Replace OrcoTexCoFactors with a matrix multiplication. */
float4x4 orco_mat = math::from_scale<float4x4>(orco_mul);
orco_mat.location() = orco_add;
int grid_id = 0;
for (const GPUMaterialAttribute *attr : attrs) {
if (STREQ(attr->name, "density")) {
sub->bind_texture(attr->input_name,
fds->tex_density ? &fds->tex_density : &module.dummy_one);
}
else if (STREQ(attr->name, "color")) {
sub->bind_texture(attr->input_name, fds->tex_color ? &fds->tex_color : &module.dummy_one);
}
else if (STR_ELEM(attr->name, "flame", "temperature")) {
sub->bind_texture(attr->input_name, fds->tex_flame ? &fds->tex_flame : &module.dummy_zero);
}
else {
sub->bind_texture(attr->input_name, module.grid_default_texture(attr->default_value));
}
volume_infos.grids_xform[grid_id++] = orco_mat;
}
bool use_constant_color = ((fds->active_fields & FLUID_DOMAIN_ACTIVE_COLORS) == 0 &&
(fds->active_fields & FLUID_DOMAIN_ACTIVE_COLOR_SET) != 0);
if (use_constant_color) {
volume_infos.color_mul = float4(UNPACK3(fds->active_color), 1.0f);
}
/* Output is such that 0..1 maps to 0..1000K. */
volume_infos.temperature_mul = fds->flame_max_temp - fds->flame_ignition;
volume_infos.temperature_bias = fds->flame_ignition;
}
if (sub) {
volume_infos.push_update();
sub->bind_ubo("drw_volume", volume_infos);
}
return sub;
}
template<typename PassType>
PassType *volume_sub_pass_implementation(PassType &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
ListBaseT<GPUMaterialAttribute> attr_list = GPU_material_attributes(gpu_material);
ListBaseWrapper<GPUMaterialAttribute> attrs(attr_list);
if (ob == nullptr) {
return volume_world_grids_init(ps, attrs);
}
if (ob->type == OB_VOLUME) {
return volume_object_grids_init(ps, ob, attrs);
}
return drw_volume_object_mesh_init(ps, scene, ob, attrs);
}
PassMain::Sub *volume_sub_pass(PassMain::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
return volume_sub_pass_implementation(ps, scene, ob, gpu_material);
}
PassSimple::Sub *volume_sub_pass(PassSimple::Sub &ps,
Scene *scene,
Object *ob,
GPUMaterial *gpu_material)
{
return volume_sub_pass_implementation(ps, scene, ob, gpu_material);
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Extraction of Mesh data into VBO to feed to GPU.
*/
#include "DNA_meshdata_types.h"
#include "ED_uvedit.hh"
#include "extract_mesh.hh"
#include "draw_cache_impl.hh"
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Extract Edit Flag Utils
* \{ */
void mesh_render_data_face_flag(const MeshRenderData &mr,
const BMFace *efa,
const BMUVOffsets &offsets,
EditLoopData &eattr)
{
if (efa == mr.efa_act) {
eattr.v_flag |= VFLAG_FACE_ACTIVE;
}
if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) {
eattr.v_flag |= VFLAG_FACE_SELECTED;
}
if (efa == mr.efa_act_uv) {
eattr.v_flag |= VFLAG_FACE_UV_ACTIVE;
}
if ((offsets.uv != -1) && (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) &&
uvedit_face_select_test_ex(mr.toolsettings, mr.bm, efa))
{
eattr.v_flag |= VFLAG_FACE_UV_SELECT;
}
#ifdef WITH_FREESTYLE
if (mr.freestyle_face_ofs != -1) {
if (BM_ELEM_CD_GET_BOOL(efa, mr.freestyle_face_ofs)) {
eattr.v_flag |= VFLAG_FACE_FREESTYLE;
}
}
#endif
}
void mesh_render_data_loop_flag(const MeshRenderData &mr,
const BMLoop *l,
const BMUVOffsets &offsets,
EditLoopData &eattr)
{
if (offsets.uv == -1) {
return;
}
if (BM_ELEM_CD_GET_BOOL(l, offsets.pin)) {
eattr.v_flag |= VFLAG_VERT_UV_PINNED;
}
if (uvedit_uv_select_test_ex(mr.toolsettings, mr.bm, l, offsets)) {
eattr.v_flag |= VFLAG_VERT_UV_SELECT;
}
}
void mesh_render_data_loop_edge_flag(const MeshRenderData &mr,
const BMLoop *l,
const BMUVOffsets &offsets,
EditLoopData &eattr)
{
if (offsets.uv == -1) {
return;
}
if (uvedit_edge_select_test_ex(mr.toolsettings, mr.bm, l, offsets)) {
eattr.v_flag |= VFLAG_EDGE_UV_SELECT;
eattr.v_flag |= VFLAG_VERT_UV_SELECT;
}
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,401 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*
* \brief Extraction of Mesh data into VBO to feed to GPU.
*/
#pragma once
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_task.hh"
#include "BLI_virtual_array.hh"
#include "DNA_scene_types.h"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "bmesh.hh"
#include "GPU_vertex_buffer.hh"
#include "draw_cache_extract.hh"
struct DRWSubdivCache;
namespace blender {
struct BMVert;
struct BMEdge;
struct BMEditMesh;
struct BMFace;
struct BMLoop;
namespace draw {
/* ---------------------------------------------------------------------- */
/** \name Mesh Render Data
* \{ */
enum class MeshExtractType {
BMesh,
Mesh,
};
struct MeshRenderData {
MeshExtractType extract_type;
int verts_num;
int edges_num;
int faces_num;
int corners_num;
int loose_edges_num;
int loose_verts_num;
int loose_indices_num;
int corner_tris_num;
int materials_num;
bool use_hide;
bool use_subsurf_fdots;
bool hide_unmapped_edges;
bool use_simplify_normals;
const ToolSettings *toolsettings;
/** Edit Mesh */
BMEditMesh *edit_bmesh;
BMesh *bm;
bke::EditMeshData *edit_data;
/* For deformed edit-mesh data. */
/* Use for #ME_WRAPPER_TYPE_BMESH. */
Span<float3> bm_vert_coords;
Span<float3> bm_vert_normals;
Span<float3> bm_face_normals;
Array<float3> bm_loop_normals;
int bm_free_normal_offset_vert = -1;
int bm_free_normal_offset_face = -1;
int bm_free_normal_offset_corner = -1;
const int *orig_index_vert;
const int *orig_index_edge;
const int *orig_index_face;
int edge_crease_ofs;
int vert_crease_ofs;
int bweight_ofs;
int freestyle_edge_ofs;
int freestyle_face_ofs;
/** Mesh */
const Mesh *mesh;
Span<float3> vert_positions;
Span<int2> edges;
OffsetIndices<int> faces;
Span<int> corner_verts;
Span<int> corner_edges;
BMVert *eve_act;
BMEdge *eed_act;
BMFace *efa_act;
BMFace *efa_act_uv;
VArraySpan<int> material_indices;
bke::MeshNormalDomain normals_domain;
Span<float3> face_normals;
Span<float3> corner_normals;
VArraySpan<bool> hide_vert;
VArraySpan<bool> hide_edge;
VArraySpan<bool> hide_poly;
VArraySpan<bool> select_vert;
VArraySpan<bool> select_edge;
VArraySpan<bool> select_poly;
VArraySpan<bool> sharp_faces;
IndexMask loose_verts;
IndexMask loose_edges;
const char *active_color_name;
const char *default_color_name;
};
inline const Mesh &editmesh_final_or_this(const Object &object, const Mesh &mesh)
{
if (mesh.runtime->edit_mesh != nullptr) {
if (const Mesh *editmesh_eval_final = BKE_object_get_editmesh_eval_final(&object)) {
return *editmesh_eval_final;
}
}
return mesh;
}
BLI_INLINE BMFace *bm_original_face_get(const MeshRenderData &mr, int idx)
{
return ((mr.orig_index_face != nullptr) && (mr.orig_index_face[idx] != ORIGINDEX_NONE) &&
mr.bm) ?
BM_face_at_index(mr.bm, mr.orig_index_face[idx]) :
nullptr;
}
BLI_INLINE BMEdge *bm_original_edge_get(const MeshRenderData &mr, int idx)
{
return ((mr.orig_index_edge != nullptr) && (mr.orig_index_edge[idx] != ORIGINDEX_NONE) &&
mr.bm) ?
BM_edge_at_index(mr.bm, mr.orig_index_edge[idx]) :
nullptr;
}
BLI_INLINE BMVert *bm_original_vert_get(const MeshRenderData &mr, int idx)
{
return ((mr.orig_index_vert != nullptr) && (mr.orig_index_vert[idx] != ORIGINDEX_NONE) &&
mr.bm) ?
BM_vert_at_index(mr.bm, mr.orig_index_vert[idx]) :
nullptr;
}
BLI_INLINE const float *bm_vert_co_get(const MeshRenderData &mr, const BMVert *eve)
{
if (!mr.bm_vert_coords.is_empty()) {
return mr.bm_vert_coords[BM_elem_index_get(eve)];
}
return eve->co;
}
BLI_INLINE const float *bm_vert_no_get(const MeshRenderData &mr, const BMVert *eve)
{
if (mr.bm_free_normal_offset_vert != -1) {
return BM_ELEM_CD_GET_FLOAT_P(eve, mr.bm_free_normal_offset_vert);
}
if (!mr.bm_vert_normals.is_empty()) {
return mr.bm_vert_normals[BM_elem_index_get(eve)];
}
return eve->no;
}
BLI_INLINE const float *bm_face_no_get(const MeshRenderData &mr, const BMFace *efa)
{
if (mr.bm_free_normal_offset_face != -1) {
return BM_ELEM_CD_GET_FLOAT_P(efa, mr.bm_free_normal_offset_face);
}
if (!mr.bm_face_normals.is_empty()) {
return mr.bm_face_normals[BM_elem_index_get(efa)];
}
return efa->no;
}
/** \} */
/* `draw_cache_extract_mesh_render_data.cc` */
/**
* \param is_editmode: When true, use the modifiers from the edit-data,
* otherwise don't use modifiers as they are not from this object.
*/
MeshRenderData mesh_render_data_create(Object &object,
Mesh &mesh,
bool is_editmode,
bool is_paint_mode,
bool do_final,
bool do_uvedit,
bool use_hide,
const ToolSettings *ts);
void mesh_render_data_update_corner_normals(MeshRenderData &mr);
void mesh_render_data_update_face_normals(MeshRenderData &mr);
void mesh_render_data_update_loose_geom(MeshRenderData &mr, MeshBufferCache &cache);
const SortedFaceData &mesh_render_data_faces_sorted_ensure(const MeshRenderData &mr,
MeshBufferCache &cache);
/* draw_cache_extract_mesh_extractors.c */
struct EditLoopData {
uchar v_flag;
uchar e_flag;
/* This is used for both vertex and edge creases. The edge crease value is stored in the bottom 4
* bits, while the vertex crease is stored in the upper 4 bits. */
uchar crease;
uchar bweight;
};
void mesh_render_data_face_flag(const MeshRenderData &mr,
const BMFace *efa,
const BMUVOffsets &offsets,
EditLoopData &eattr);
void mesh_render_data_loop_flag(const MeshRenderData &mr,
const BMLoop *l,
const BMUVOffsets &offsets,
EditLoopData &eattr);
void mesh_render_data_loop_edge_flag(const MeshRenderData &mr,
const BMLoop *l,
const BMUVOffsets &offsets,
EditLoopData &eattr);
/* In the GPU vertex buffers, the value for each vertex is duplicated to each of its vertex
* corners. So the edges on the GPU connect face corners rather than vertices. */
inline uint2 edge_from_corners(const IndexRange face, const int corner)
{
const int corner_next = bke::mesh::face_corner_next(face, corner);
return uint2(corner, corner_next);
}
template<typename T>
void extract_mesh_loose_edge_data(const Span<T> vert_data,
const Span<int2> edges,
const IndexMask &loose_edges,
MutableSpan<T> gpu_data)
{
loose_edges.foreach_index_optimized<int>(
[&](const int i, const int pos) {
const int2 edge = edges[i];
gpu_data[pos * 2 + 0] = vert_data[edge[0]];
gpu_data[pos * 2 + 1] = vert_data[edge[1]];
},
exec_mode::grain_size(4096));
}
gpu::VertBufPtr extract_positions(const MeshRenderData &mr);
gpu::VertBufPtr extract_positions_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::VertBufPtr *orco_vbo);
gpu::VertBufPtr extract_face_dots_position(const MeshRenderData &mr);
void extract_face_dots_subdiv(const DRWSubdivCache &subdiv_cache,
gpu::VertBufPtr &fdots_pos,
gpu::VertBufPtr *fdots_nor,
gpu::IndexBufPtr &fdots);
gpu::VertBufPtr extract_normals(const MeshRenderData &mr, bool use_hq);
gpu::VertBufPtr extract_normals_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
gpu::VertBuf &pos);
gpu::VertBufPtr extract_vert_normals(const MeshRenderData &mr);
gpu::VertBufPtr extract_face_dot_normals(const MeshRenderData &mr, bool use_hq);
gpu::VertBufPtr extract_edge_factor(const MeshRenderData &mr);
gpu::VertBufPtr extract_edge_factor_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::VertBuf &pos);
gpu::IndexBufPtr extract_tris(const MeshRenderData &mr, const SortedFaceData &face_sorted);
void create_material_subranges(const SortedFaceData &face_sorted,
gpu::IndexBuf &tris_ibo,
MutableSpan<gpu::IndexBufPtr> ibos);
gpu::IndexBufPtr extract_tris_subdiv(const DRWSubdivCache &subdiv_cache, MeshBatchCache &cache);
void extract_lines(const MeshRenderData &mr,
gpu::IndexBufPtr *lines,
gpu::IndexBufPtr *lines_loose,
bool &no_loose_wire);
void extract_lines_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::IndexBufPtr *lines,
gpu::IndexBufPtr *lines_loose,
bool &no_loose_wire);
gpu::IndexBufPtr extract_points(const MeshRenderData &mr);
gpu::IndexBufPtr extract_points_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
gpu::VertBufPtr extract_edit_data(const MeshRenderData &mr);
gpu::VertBufPtr extract_edit_data_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
gpu::VertBufPtr extract_tangents(const MeshRenderData &mr,
const MeshBatchCache &cache,
bool use_hq);
gpu::VertBufPtr extract_tangents_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache);
gpu::VertBufPtr extract_vert_index(const MeshRenderData &mr);
gpu::VertBufPtr extract_edge_index(const MeshRenderData &mr);
gpu::VertBufPtr extract_face_index(const MeshRenderData &mr);
gpu::VertBufPtr extract_face_dot_index(const MeshRenderData &mr);
gpu::VertBufPtr extract_vert_index_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr);
gpu::VertBufPtr extract_edge_index_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr);
gpu::VertBufPtr extract_face_index_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr);
gpu::VertBufPtr extract_weights(const MeshRenderData &mr, const MeshBatchCache &cache);
gpu::VertBufPtr extract_weights_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache);
gpu::IndexBufPtr extract_face_dots(const MeshRenderData &mr);
gpu::VertBufPtr extract_face_dots_uv(const MeshRenderData &mr);
gpu::VertBufPtr extract_face_dots_edituv_data(const MeshRenderData &mr);
gpu::IndexBufPtr extract_lines_paint_mask(const MeshRenderData &mr);
gpu::IndexBufPtr extract_lines_paint_mask_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
gpu::IndexBufPtr extract_lines_adjacency(const MeshRenderData &mr, bool &r_is_manifold);
gpu::IndexBufPtr extract_lines_adjacency_subdiv(const DRWSubdivCache &subdiv_cache,
bool &r_is_manifold);
gpu::VertBufPtr extract_uv_maps(const MeshRenderData &mr, const MeshBatchCache &cache);
gpu::VertBufPtr extract_uv_maps_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache);
gpu::VertBufPtr extract_edituv_stretch_area(const MeshRenderData &mr,
float &tot_area,
float &tot_uv_area);
gpu::VertBufPtr extract_edituv_stretch_area_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
float &tot_area,
float &tot_uv_area);
gpu::VertBufPtr extract_edituv_stretch_angle(const MeshRenderData &mr);
gpu::VertBufPtr extract_edituv_stretch_angle_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache);
gpu::VertBufPtr extract_edituv_data(const MeshRenderData &mr);
gpu::VertBufPtr extract_edituv_data_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
gpu::IndexBufPtr extract_edituv_tris(const MeshRenderData &mr, bool edit_uvs);
gpu::IndexBufPtr extract_edituv_tris_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
enum class UvExtractionMode : int8_t {
Edit,
Selection,
All,
};
gpu::IndexBufPtr extract_edituv_lines(const MeshRenderData &mr, UvExtractionMode mode);
gpu::IndexBufPtr extract_edituv_lines_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
UvExtractionMode mode);
gpu::IndexBufPtr extract_edituv_points(const MeshRenderData &mr);
gpu::IndexBufPtr extract_edituv_points_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
gpu::IndexBufPtr extract_edituv_face_dots(const MeshRenderData &mr);
gpu::VertBufPtr extract_mesh_analysis(const MeshRenderData &mr, const float4x4 &object_to_world);
gpu::VertBufPtr extract_skin_roots(const MeshRenderData &mr);
gpu::VertBufPtr extract_sculpt_data(const MeshRenderData &mr);
gpu::VertBufPtr extract_sculpt_data_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
gpu::VertBufPtr extract_orco(const MeshRenderData &mr);
gpu::VertBufPtr extract_attribute(const MeshRenderData &mr, StringRef name);
gpu::VertBufPtr extract_attribute_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
StringRef name);
gpu::VertBufPtr extract_attr_viewer(const MeshRenderData &mr);
gpu::VertBufPtr extract_paint_overlay_flags(const MeshRenderData &mr);
gpu::VertBufPtr extract_paint_overlay_flags_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache);
} // namespace draw
} // namespace blender

View File

@@ -0,0 +1,703 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_editmesh.hh"
#include "BLI_math_geom.h"
#include "extract_mesh.hh"
#include "GPU_index_buffer.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Extract Edit UV Triangles Indices
* \{ */
inline bool skip_bm_face(const BMFace &face, const bool sync_selection)
{
if (BM_elem_flag_test(&face, BM_ELEM_HIDDEN)) {
return true;
}
if (!sync_selection) {
if (!BM_elem_flag_test_bool(&face, BM_ELEM_SELECT)) {
return true;
}
}
return false;
}
static OffsetIndices<int> build_bmesh_face_offsets(const BMesh &bm, Array<int> &r_offset_data)
{
r_offset_data.reinitialize(bm.totface + 1);
threading::parallel_for(IndexRange(bm.totface), 4096, [&](const IndexRange range) {
for (const int face : range) {
r_offset_data[face] = BM_face_at_index(&const_cast<BMesh &>(bm), face)->len;
}
});
return offset_indices::accumulate_counts_to_offsets(r_offset_data);
}
static gpu::IndexBufPtr extract_edituv_tris_bm(const MeshRenderData &mr, const bool sync_selection)
{
const Span<std::array<BMLoop *, 3>> looptris = mr.edit_bmesh->looptris;
const BMesh &bm = *mr.bm;
IndexMaskMemory memory;
const IndexMask selection = IndexMask::from_predicate(
IndexRange(bm.totface), memory, [&](const int face) {
return !skip_bm_face(*BM_face_at_index(&const_cast<BMesh &>(bm), face), sync_selection);
});
if (selection.size() == bm.totface) {
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_TRIS, looptris.size(), mr.corners_num);
MutableSpan<uint3> data = GPU_indexbuf_get_data(&builder).cast<uint3>();
threading::parallel_for(looptris.index_range(), 4096, [&](const IndexRange range) {
for (const int i : range) {
data[i] = uint3(BM_elem_index_get(looptris[i][0]),
BM_elem_index_get(looptris[i][1]),
BM_elem_index_get(looptris[i][2]));
}
});
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, mr.corners_num, false));
}
Array<int> face_offset_data;
const OffsetIndices faces = build_bmesh_face_offsets(bm, face_offset_data);
Array<int> selected_face_offset_data(selection.size() + 1);
const OffsetIndices selected_faces = offset_indices::gather_selected_offsets(
faces, selection, selected_face_offset_data);
const int tris_num = poly_to_tri_count(selected_faces.size(), selected_faces.total_size());
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_TRIS, tris_num, mr.corners_num);
MutableSpan<uint3> data = GPU_indexbuf_get_data(&builder).cast<uint3>();
selection.foreach_index(
[&](const int face, const int mask) {
const IndexRange tris = bke::mesh::face_triangles_range(faces, face);
const IndexRange ibo_tris = bke::mesh::face_triangles_range(selected_faces, mask);
for (const int i : tris.index_range()) {
data[ibo_tris[i]] = uint3(BM_elem_index_get(looptris[tris[i]][0]),
BM_elem_index_get(looptris[tris[i]][1]),
BM_elem_index_get(looptris[tris[i]][2]));
}
},
exec_mode::grain_size(4096));
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, mr.corners_num, false));
}
static gpu::IndexBufPtr extract_edituv_tris_mesh(const MeshRenderData &mr,
const bool sync_selection)
{
const OffsetIndices faces = mr.faces;
const Span<int3> corner_tris = mr.mesh->corner_tris();
IndexMaskMemory memory;
IndexMask selection;
if (mr.bm) {
selection = IndexMask::from_predicate(faces.index_range(), memory, [&](const int face) {
const BMFace *face_orig = bm_original_face_get(mr, face);
if (!face_orig) {
return false;
}
if (skip_bm_face(*face_orig, sync_selection)) {
return false;
}
return true;
});
}
else {
if (mr.hide_poly.is_empty()) {
selection = faces.index_range();
}
else {
selection = IndexMask::from_bools_inverse(faces.index_range(), mr.hide_poly, memory);
}
if (!sync_selection && !mr.select_poly.is_empty()) {
selection = IndexMask::from_bools(selection, mr.select_poly, memory);
}
}
if (selection.size() == faces.size()) {
return gpu::IndexBufPtr(GPU_indexbuf_build_from_memory(GPU_PRIM_TRIS,
corner_tris.cast<uint32_t>().data(),
corner_tris.size(),
0,
mr.corners_num,
false));
}
Array<int> selected_face_offset_data(selection.size() + 1);
const OffsetIndices selected_faces = offset_indices::gather_selected_offsets(
faces, selection, selected_face_offset_data);
const int tris_num = poly_to_tri_count(selected_faces.size(), selected_faces.total_size());
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_TRIS, tris_num, mr.corners_num);
MutableSpan<uint3> data = GPU_indexbuf_get_data(&builder).cast<uint3>();
selection.foreach_index(
[&](const int face, const int mask) {
const IndexRange tris = bke::mesh::face_triangles_range(faces, face);
const IndexRange ibo_tris = bke::mesh::face_triangles_range(selected_faces, mask);
for (const int i : tris.index_range()) {
data[ibo_tris[i]] = uint3(
corner_tris[tris[i]][0], corner_tris[tris[i]][1], corner_tris[tris[i]][2]);
}
},
exec_mode::grain_size(4096));
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, mr.corners_num, false));
}
gpu::IndexBufPtr extract_edituv_tris(const MeshRenderData &mr, const bool edit_uvs)
{
const bool sync_selection = edit_uvs ? (mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0 :
false;
if (mr.extract_type == MeshExtractType::BMesh) {
return extract_edituv_tris_bm(mr, sync_selection);
}
return extract_edituv_tris_mesh(mr, sync_selection);
}
static gpu::IndexBufPtr build_tris_from_subdiv_quad_selection(const DRWSubdivCache &subdiv_cache,
const IndexMask &selection)
{
const int tris_num = selection.size() * 2;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_TRIS, tris_num, subdiv_cache.num_subdiv_loops);
MutableSpan<uint3> data = GPU_indexbuf_get_data(&builder).cast<uint3>();
selection.foreach_index(
[&](const int subdiv_quad_index, const int mask) {
const uint corner_start = subdiv_quad_index * 4;
data[mask * 2 + 0] = uint3(corner_start, corner_start + 1, corner_start + 2);
data[mask * 2 + 1] = uint3(corner_start, corner_start + 2, corner_start + 3);
},
exec_mode::grain_size(4096));
return gpu::IndexBufPtr(
GPU_indexbuf_build_ex(&builder, 0, subdiv_cache.num_subdiv_loops, false));
}
static gpu::IndexBufPtr extract_edituv_tris_subdiv_bm(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const bool sync_selection)
{
const BMesh &bm = *mr.bm;
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
IndexMaskMemory memory;
const IndexMask selection = IndexMask::from_predicate(
IndexRange(subdiv_cache.num_subdiv_quads), memory, [&](const int subdiv_quad_index) {
const uint corner_start = subdiv_quad_index * 4;
const int coarse_face = subdiv_loop_face_index[corner_start];
const BMFace &bm_face = *BM_face_at_index(&const_cast<BMesh &>(bm), coarse_face);
return !skip_bm_face(bm_face, sync_selection);
});
return build_tris_from_subdiv_quad_selection(subdiv_cache, selection);
}
static gpu::IndexBufPtr extract_edituv_tris_subdiv_mesh(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const bool sync_selection)
{
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
IndexMaskMemory memory;
const IndexMask selection = IndexMask::from_predicate(
IndexRange(subdiv_cache.num_subdiv_quads), memory, [&](const int subdiv_quad_index) {
const uint corner_start = subdiv_quad_index * 4;
const int coarse_face = subdiv_loop_face_index[corner_start];
const BMFace *face_orig = bm_original_face_get(mr, coarse_face);
if (!face_orig) {
return false;
}
if (skip_bm_face(*face_orig, sync_selection)) {
return false;
}
return true;
});
return build_tris_from_subdiv_quad_selection(subdiv_cache, selection);
}
gpu::IndexBufPtr extract_edituv_tris_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
const bool sync_selection = (mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
if (mr.extract_type == MeshExtractType::BMesh) {
return extract_edituv_tris_subdiv_bm(mr, subdiv_cache, sync_selection);
}
return extract_edituv_tris_subdiv_mesh(mr, subdiv_cache, sync_selection);
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Extract Edit UV Line Indices around faces
* \{ */
static gpu::IndexBufPtr extract_edituv_lines_bm(const MeshRenderData &mr,
const bool sync_selection)
{
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, mr.corners_num, mr.corners_num);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
int line_index = 0;
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, mr.bm, BM_FACES_OF_MESH) {
if (skip_bm_face(*face, sync_selection)) {
continue;
}
const BMLoop *loop = BM_FACE_FIRST_LOOP(face);
for ([[maybe_unused]] const int i : IndexRange(face->len)) {
data[line_index++] = uint2(BM_elem_index_get(loop), BM_elem_index_get(loop->next));
loop = loop->next;
}
}
/* Only upload the part of the index buffer that is used. Alternatively it might be beneficial to
* count the number of visible edges first, especially if that allows parallelizing filling the
* data array. */
builder.index_len = line_index * 2;
builder.index_min = 0;
builder.index_max = mr.corners_num;
builder.uses_restart_indices = false;
gpu::IndexBufPtr result = gpu::IndexBufPtr(GPU_indexbuf_calloc());
GPU_indexbuf_build_in_place(&builder, result.get());
return result;
}
static gpu::IndexBufPtr extract_edituv_lines_mesh(const MeshRenderData &mr,
const bool sync_selection)
{
const OffsetIndices faces = mr.faces;
const Span<int> corner_edges = mr.corner_edges;
const Span<int> orig_index_edge = mr.orig_index_edge ?
Span<int>(mr.orig_index_edge, mr.edges_num) :
Span<int>();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, mr.corners_num, mr.corners_num);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
int line_index = 0;
if (mr.bm) {
for (const int face_index : faces.index_range()) {
const IndexRange face = faces[face_index];
const BMFace *face_orig = bm_original_face_get(mr, face_index);
if (!face_orig) {
continue;
}
if (skip_bm_face(*face_orig, sync_selection)) {
continue;
}
for (const int corner : face) {
const int edge = corner_edges[corner];
if (!orig_index_edge.is_empty() && orig_index_edge[edge] == ORIGINDEX_NONE) {
continue;
}
data[line_index++] = edge_from_corners(face, corner);
}
}
}
else {
IndexMaskMemory memory;
IndexMask visible = faces.index_range();
if (!mr.hide_poly.is_empty()) {
visible = IndexMask::from_bools_inverse(visible, mr.hide_poly, memory);
}
if (!sync_selection) {
if (mr.select_poly.is_empty()) {
visible = {};
}
else {
visible = IndexMask::from_bools(visible, mr.select_poly, memory);
}
}
visible.foreach_index([&](const int face_index) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
const int edge = corner_edges[corner];
if (!orig_index_edge.is_empty() && orig_index_edge[edge] == ORIGINDEX_NONE) {
continue;
}
data[line_index++] = edge_from_corners(face, corner);
}
});
}
/* Only upload the part of the index buffer that is used. Alternatively it might be beneficial to
* count the number of visible edges first, especially if that allows parallelizing filling the
* data array. */
builder.index_len = line_index * 2;
builder.index_min = 0;
builder.index_max = mr.corners_num;
builder.uses_restart_indices = false;
gpu::IndexBufPtr result = gpu::IndexBufPtr(GPU_indexbuf_calloc());
GPU_indexbuf_build_in_place(&builder, result.get());
return result;
}
gpu::IndexBufPtr extract_edituv_lines(const MeshRenderData &mr, const UvExtractionMode mode)
{
bool sync_selection = false;
switch (mode) {
case UvExtractionMode::All:
sync_selection = true;
break;
case UvExtractionMode::Edit:
sync_selection = ((mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0);
break;
case UvExtractionMode::Selection:
sync_selection = false;
break;
}
if (mr.extract_type == MeshExtractType::BMesh) {
return extract_edituv_lines_bm(mr, sync_selection);
}
return extract_edituv_lines_mesh(mr, sync_selection);
}
static gpu::IndexBufPtr extract_edituv_lines_subdiv_bm(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const bool sync_selection)
{
const BMesh &bm = *mr.bm;
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
GPUIndexBufBuilder builder;
GPU_indexbuf_init(
&builder, GPU_PRIM_LINES, subdiv_cache.num_subdiv_loops, subdiv_cache.num_subdiv_loops);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
int line_index = 0;
for (const int subdiv_quad : IndexRange(subdiv_cache.num_subdiv_quads)) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
const BMFace &face_orig = *BM_face_at_index(&const_cast<BMesh &>(bm), coarse_face);
if (skip_bm_face(face_orig, sync_selection)) {
continue;
}
const IndexRange subdiv_face(subdiv_quad * 4, 4);
for (const int subdiv_corner : subdiv_face) {
const int coarse_edge = subdiv_loop_edge_index[subdiv_corner];
if (coarse_edge == -1) {
continue;
}
data[line_index++] = edge_from_corners(subdiv_face, subdiv_corner);
}
}
return gpu::IndexBufPtr(
GPU_indexbuf_build_ex(&builder, 0, subdiv_cache.num_subdiv_loops, false));
}
static gpu::IndexBufPtr extract_edituv_lines_subdiv_mesh(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const bool sync_selection)
{
GPUIndexBufBuilder builder;
GPU_indexbuf_init(
&builder, GPU_PRIM_LINES, subdiv_cache.num_subdiv_loops, subdiv_cache.num_subdiv_loops);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
int line_index = 0;
/* NOTE: #subdiv_loop_edge_index already has the #CD_ORIGINDEX layer baked in. */
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
/* TODO: Replace subdiv quad iteration with coarse face iteration. */
for (const int subdiv_quad : IndexRange(subdiv_cache.num_subdiv_quads)) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
if (const BMesh *bm = mr.bm) {
const int orig_coarse_face = mr.orig_index_face ? mr.orig_index_face[coarse_face] :
coarse_face;
const BMFace &face_orig = *BM_face_at_index(const_cast<BMesh *>(bm), orig_coarse_face);
if (skip_bm_face(face_orig, sync_selection)) {
continue;
}
}
else {
if (!mr.hide_poly.is_empty() && mr.hide_poly[coarse_face]) {
continue;
}
if (!sync_selection) {
if (mr.select_poly.is_empty() || !mr.select_poly[coarse_face]) {
continue;
}
}
}
const IndexRange subdiv_face(subdiv_quad * 4, 4);
for (const int subdiv_corner : subdiv_face) {
const int coarse_edge = subdiv_loop_edge_index[subdiv_corner];
if (coarse_edge == -1) {
continue;
}
data[line_index++] = edge_from_corners(subdiv_face, subdiv_corner);
}
}
/* Only upload the part of the index buffer that is used. Alternatively it might be beneficial to
* count the number of visible edges first, especially if that allows parallelizing filling the
* data array. */
builder.index_len = line_index * 2;
builder.index_min = 0;
builder.index_max = subdiv_cache.num_subdiv_loops;
builder.uses_restart_indices = false;
gpu::IndexBufPtr result = gpu::IndexBufPtr(GPU_indexbuf_calloc());
GPU_indexbuf_build_in_place(&builder, result.get());
return result;
}
gpu::IndexBufPtr extract_edituv_lines_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const UvExtractionMode mode)
{
bool sync_selection = false;
switch (mode) {
case UvExtractionMode::All:
sync_selection = true;
break;
case UvExtractionMode::Edit:
sync_selection = ((mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0);
break;
case UvExtractionMode::Selection:
sync_selection = false;
break;
}
if (mr.extract_type == MeshExtractType::BMesh) {
return extract_edituv_lines_subdiv_bm(mr, subdiv_cache, sync_selection);
}
return extract_edituv_lines_subdiv_mesh(mr, subdiv_cache, sync_selection);
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Extract Edit UV Points Indices
* \{ */
static void extract_edituv_points_bm(const MeshRenderData &mr,
const bool sync_selection,
GPUIndexBufBuilder &builder)
{
const BMesh &bm = *mr.bm;
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, &const_cast<BMesh &>(bm), BM_FACES_OF_MESH) {
if (skip_bm_face(*face, sync_selection)) {
continue;
}
const BMLoop *loop = BM_FACE_FIRST_LOOP(face);
for ([[maybe_unused]] const int i : IndexRange(face->len)) {
GPU_indexbuf_add_point_vert(&builder, BM_elem_index_get(loop));
loop = loop->next;
}
}
}
static void extract_edituv_points_mesh(const MeshRenderData &mr,
const bool sync_selection,
GPUIndexBufBuilder &builder)
{
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Span<int> orig_index_vert = mr.orig_index_vert ?
Span<int>(mr.orig_index_vert, mr.verts_num) :
Span<int>();
for (const int face_index : faces.index_range()) {
const BMFace *face_orig = bm_original_face_get(mr, face_index);
if (!face_orig) {
continue;
}
if (skip_bm_face(*face_orig, sync_selection)) {
continue;
}
for (const int corner : faces[face_index]) {
const int vert = corner_verts[corner];
if (!orig_index_vert.is_empty() && orig_index_vert[vert] == ORIGINDEX_NONE) {
continue;
}
GPU_indexbuf_add_point_vert(&builder, corner);
}
}
}
gpu::IndexBufPtr extract_edituv_points(const MeshRenderData &mr)
{
const bool sync_selection = (mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, mr.corners_num, mr.corners_num);
if (mr.extract_type == MeshExtractType::BMesh) {
extract_edituv_points_bm(mr, sync_selection, builder);
}
else {
extract_edituv_points_mesh(mr, sync_selection, builder);
}
return gpu::IndexBufPtr(GPU_indexbuf_build(&builder));
}
static void extract_edituv_points_subdiv_bm(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const bool sync_selection,
GPUIndexBufBuilder &builder)
{
const BMesh &bm = *mr.bm;
const Span<int> subdiv_loop_vert_index = subdiv_cache.verts_orig_index->data<int>();
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
for (const int subdiv_quad : IndexRange(subdiv_cache.num_subdiv_quads)) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
const BMFace &face_orig = *BM_face_at_index(&const_cast<BMesh &>(bm), coarse_face);
if (skip_bm_face(face_orig, sync_selection)) {
continue;
}
for (const int subdiv_corner : IndexRange(subdiv_quad * 4, 4)) {
const int coarse_vert = subdiv_loop_vert_index[subdiv_corner];
if (coarse_vert == -1) {
continue;
}
GPU_indexbuf_add_point_vert(&builder, subdiv_corner);
}
}
}
static void extract_edituv_points_subdiv_mesh(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const bool sync_selection,
GPUIndexBufBuilder &builder)
{
const Span<int> subdiv_loop_vert_index = subdiv_cache.verts_orig_index->data<int>();
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
for (const int subdiv_quad : IndexRange(subdiv_cache.num_subdiv_quads)) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
const BMFace *face_orig = bm_original_face_get(mr, coarse_face);
if (!face_orig) {
continue;
}
if (skip_bm_face(*face_orig, sync_selection)) {
continue;
}
for (const int subdiv_corner : IndexRange(subdiv_quad * 4, 4)) {
const int coarse_vert = subdiv_loop_vert_index[subdiv_corner];
if (coarse_vert == -1) {
continue;
}
GPU_indexbuf_add_point_vert(&builder, subdiv_corner);
}
}
}
gpu::IndexBufPtr extract_edituv_points_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
const bool sync_selection = (mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(
&builder, GPU_PRIM_POINTS, subdiv_cache.num_subdiv_loops, subdiv_cache.num_subdiv_loops);
if (mr.extract_type == MeshExtractType::BMesh) {
extract_edituv_points_subdiv_bm(mr, subdiv_cache, sync_selection, builder);
}
else {
extract_edituv_points_subdiv_mesh(mr, subdiv_cache, sync_selection, builder);
}
return gpu::IndexBufPtr(GPU_indexbuf_build(&builder));
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Extract Edit UV Face-dots Indices
* \{ */
static gpu::IndexBufPtr extract_edituv_face_dots_bm(const MeshRenderData &mr,
const bool sync_selection)
{
const BMesh &bm = *mr.bm;
IndexMaskMemory memory;
const IndexMask visible = IndexMask::from_predicate(
IndexMask(bm.totface), memory, [&](const int i) {
return !skip_bm_face(*BM_face_at_index(&const_cast<BMesh &>(bm), i), sync_selection);
});
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, visible.size(), bm.totface);
visible.to_indices(GPU_indexbuf_get_data(&builder).cast<int>());
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, bm.totface, false));
}
static gpu::IndexBufPtr extract_edituv_face_dots_mesh(const MeshRenderData &mr,
const bool sync_selection)
{
const OffsetIndices faces = mr.faces;
IndexMaskMemory memory;
IndexMask visible = IndexMask::from_predicate(faces.index_range(), memory, [&](const int i) {
const BMFace *face_orig = bm_original_face_get(mr, i);
if (!face_orig) {
return false;
}
if (skip_bm_face(*face_orig, sync_selection)) {
return false;
}
return true;
});
if (mr.use_subsurf_fdots) {
const BitSpan facedot_tags = mr.mesh->runtime->subsurf_face_dot_tags;
const Span<int> corner_verts = mr.corner_verts;
visible = IndexMask::from_predicate(visible, memory, [&](const int i) {
const Span<int> face_verts = corner_verts.slice(faces[i]);
return std::any_of(face_verts.begin(), face_verts.end(), [&](const int vert) {
return facedot_tags[vert];
});
});
}
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, visible.size(), faces.size());
visible.to_indices(GPU_indexbuf_get_data(&builder).cast<int>());
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, faces.size(), false));
}
gpu::IndexBufPtr extract_edituv_face_dots(const MeshRenderData &mr)
{
const bool sync_selection = (mr.toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) != 0;
if (mr.extract_type == MeshExtractType::BMesh) {
return extract_edituv_face_dots_bm(mr, sync_selection);
}
return extract_edituv_face_dots_mesh(mr, sync_selection);
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "GPU_index_buffer.hh"
#include "extract_mesh.hh"
namespace blender::draw {
static IndexMask calc_face_visibility_mesh(const MeshRenderData &mr, IndexMaskMemory &memory)
{
IndexMask visible(mr.faces_num);
if (!mr.hide_poly.is_empty()) {
visible = IndexMask::from_bools_inverse(visible, mr.hide_poly, memory);
}
if (mr.use_subsurf_fdots) {
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const BitSpan facedot_tags = mr.mesh->runtime->subsurf_face_dot_tags;
visible = IndexMask::from_predicate(visible, memory, [&](const int i) {
const Span<int> face_verts = corner_verts.slice(faces[i]);
return std::any_of(face_verts.begin(), face_verts.end(), [&](const int vert) {
return facedot_tags[vert];
});
});
}
return visible;
}
static gpu::IndexBufPtr index_mask_to_ibo(const IndexMask &mask)
{
const int max_index = mask.min_array_size();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, mask.size(), max_index);
MutableSpan<uint> data = GPU_indexbuf_get_data(&builder);
mask.to_indices<int>(data.cast<int>());
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
}
static gpu::IndexBufPtr extract_face_dots_mesh(const MeshRenderData &mr)
{
IndexMaskMemory memory;
const IndexMask visible_faces = calc_face_visibility_mesh(mr, memory);
return index_mask_to_ibo(visible_faces);
}
static gpu::IndexBufPtr extract_face_dots_bm(const MeshRenderData &mr)
{
BMesh &bm = *mr.bm;
IndexMaskMemory memory;
const IndexMask visible_faces = IndexMask::from_predicate(
IndexRange(bm.totface), memory, [&](const int i) {
return !BM_elem_flag_test_bool(BM_face_at_index(&bm, i), BM_ELEM_HIDDEN);
});
return index_mask_to_ibo(visible_faces);
}
gpu::IndexBufPtr extract_face_dots(const MeshRenderData &mr)
{
if (mr.extract_type == MeshExtractType::Mesh) {
return extract_face_dots_mesh(mr);
}
return extract_face_dots_bm(mr);
}
} // namespace blender::draw

View File

@@ -0,0 +1,358 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "GPU_index_buffer.hh"
#include "extract_mesh.hh"
#include "draw_cache_inline.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static IndexMask calc_mesh_edge_visibility(const MeshRenderData &mr,
const IndexMask &mask,
IndexMaskMemory &memory)
{
IndexMask visible = mask;
if (!mr.mesh->runtime->subsurf_optimal_display_edges.is_empty()) {
const BoundedBitSpan visible_bits = mr.mesh->runtime->subsurf_optimal_display_edges;
visible = IndexMask::from_bits(visible, visible_bits, memory);
}
if (!mr.hide_edge.is_empty()) {
visible = IndexMask::from_bools_inverse(visible, mr.hide_edge, memory);
}
if (mr.hide_unmapped_edges && mr.orig_index_edge != nullptr) {
const int *orig_index = mr.orig_index_edge;
static_assert(ORIGINDEX_NONE == -1);
visible = array_utils::indices_non_negative(visible, Span(orig_index, mr.edges_num), memory);
}
return visible;
}
static void fill_loose_lines_ibo(const MeshRenderData &mr,
const IndexMask &visible,
MutableSpan<uint2> data)
{
/* Vertices for loose edges are not shared in the GPU vertex buffers, so the indices are simply
* an increasing contiguous range. Ideally this would be generated on the GPU itself, or just
* unnecessary, but a large number of loose edges isn't expected to be a common performance
* bottleneck either. */
threading::memory_bandwidth_bound_task(data.size_in_bytes(), [&]() {
const uint loose_start = mr.corners_num;
if (visible.size() == mr.loose_edges.size()) {
array_utils::fill_index_range(data.cast<uint>(), loose_start);
}
else {
visible.foreach_index_optimized<int>(
[&](const int i, const int pos) {
data[pos] = loose_start + uint2(i * 2 + 0, i * 2 + 1);
},
exec_mode::grain_size(4096));
}
});
}
static IndexMask calc_visible_loose_edge_indices(const MeshRenderData &mr, IndexMaskMemory &memory)
{
/* Since we're building a mask of the full loose edge data arrays (i.e. rather than a mask
* of the mesh's edges) we need random access to the IndexMask indices. */
Array<int, 64> loose_edges(mr.loose_edges.size());
mr.loose_edges.to_indices(loose_edges.as_mutable_span());
IndexMask visible(loose_edges.size());
if (!mr.hide_edge.is_empty()) {
const Span<bool> hide_edge = mr.hide_edge;
visible = IndexMask::from_predicate(
visible, memory, [&](const int i) { return !hide_edge[loose_edges[i]]; });
}
if (mr.hide_unmapped_edges && mr.orig_index_edge != nullptr) {
const int *orig_index = mr.orig_index_edge;
visible = IndexMask::from_predicate(visible, memory, [&](const int64_t i) {
return orig_index[loose_edges[i]] != ORIGINDEX_NONE;
});
}
return visible;
}
static void extract_lines_mesh(const MeshRenderData &mr,
gpu::IndexBufPtr *lines,
gpu::IndexBufPtr *lines_loose,
bool &no_loose_wire)
{
IndexMaskMemory memory;
const IndexMask visible_loose_edges = calc_visible_loose_edge_indices(mr, memory);
const int max_index = mr.corners_num + mr.loose_edges.size() * 2;
no_loose_wire = visible_loose_edges.is_empty();
if (lines_loose && !lines) {
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, visible_loose_edges.size(), max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
fill_loose_lines_ibo(mr, visible_loose_edges, data);
*lines_loose = gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
return;
}
const IndexMask non_loose_edges = mr.loose_edges.complement(IndexRange(mr.edges_num), memory);
const IndexMask visible_non_loose_edges = calc_mesh_edge_visibility(mr, non_loose_edges, memory);
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder,
GPU_PRIM_LINES,
visible_non_loose_edges.size() + visible_loose_edges.size(),
max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
/* This code fills the index buffer in a non-deterministic way, with non-atomic access to `used`.
* This is okay because any of the possible face corner indices are correct, since they all
* correspond to the same #Mesh vertex. `used` exists here as a performance optimization to
* avoid writing to the VBO. */
const OffsetIndices faces = mr.faces;
const Span<int> corner_edges = mr.corner_edges;
if (visible_non_loose_edges.size() == mr.edges_num) {
/* All edges in the mesh are visible. The edges in the GPU buffer will have the same order as
* the mesh's edges, so any remapping is unnecessary. Use a boolean array to avoid writing to
* the same indices again multiple times from different threads. This is slightly beneficial
* because booleans are 8 times smaller than the `uint2` for each edge. */
Array<bool> used(mr.edges_num, false);
threading::memory_bandwidth_bound_task(
used.as_span().size_in_bytes() + data.size_in_bytes() + corner_edges.size_in_bytes(),
[&]() {
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
const int edge = corner_edges[corner];
if (!used[edge]) {
used[edge] = true;
data[edge] = edge_from_corners(face, corner);
}
}
}
});
});
}
else {
Array<int> map(mr.edges_num, -1);
threading::memory_bandwidth_bound_task(
map.as_span().size_in_bytes() + data.size_in_bytes() + corner_edges.size_in_bytes(),
[&]() {
index_mask::build_reverse_map(visible_non_loose_edges, map.as_mutable_span());
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
const int edge = corner_edges[corner];
const int vbo_index = map[edge];
if (vbo_index != -1) {
map[edge] = -1;
data[vbo_index] = edge_from_corners(face, corner);
}
}
}
});
});
}
fill_loose_lines_ibo(mr, visible_loose_edges, data.take_back(visible_loose_edges.size()));
*lines = gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
if (lines_loose) {
*lines_loose = gpu::IndexBufPtr(GPU_indexbuf_create_subrange(
lines->get(), visible_non_loose_edges.size() * 2, visible_loose_edges.size() * 2));
}
}
static void extract_lines_bm(const MeshRenderData &mr,
gpu::IndexBufPtr *lines,
gpu::IndexBufPtr *lines_loose,
bool &no_loose_wire)
{
const BMesh &bm = *mr.bm;
Array<int, 64> loose_edges(mr.loose_edges.size());
mr.loose_edges.to_indices(loose_edges.as_mutable_span());
IndexMaskMemory memory;
const IndexMask visible_loose_edges = IndexMask::from_predicate(
loose_edges.index_range(), memory, [&](const int i) {
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), loose_edges[i]);
return !BM_elem_flag_test_bool(&edge, BM_ELEM_HIDDEN);
});
const int max_index = mr.corners_num + loose_edges.size() * 2;
no_loose_wire = visible_loose_edges.is_empty();
if (lines_loose && !lines) {
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, visible_loose_edges.size(), max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
fill_loose_lines_ibo(mr, visible_loose_edges, data);
*lines_loose = gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
return;
}
const IndexMask non_loose_edges = mr.loose_edges.complement(IndexRange(bm.totedge), memory);
const IndexMask visible_non_loose_edges = IndexMask::from_predicate(
non_loose_edges, memory, [&](const int i) {
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), i);
return !BM_elem_flag_test_bool(&edge, BM_ELEM_HIDDEN);
});
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder,
GPU_PRIM_LINES,
visible_non_loose_edges.size() + visible_loose_edges.size(),
max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
/* Make use of BMesh's edge to loop topology knowledge to iterate over edges instead of
* iterating over faces and defining edges implicitly as done in the #Mesh extraction. */
visible_non_loose_edges.foreach_index(
[&](const int i, const int pos) {
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), i);
data[pos] = uint2(BM_elem_index_get(edge.l), BM_elem_index_get(edge.l->next));
},
exec_mode::grain_size(4096));
fill_loose_lines_ibo(mr, visible_loose_edges, data.take_back(visible_loose_edges.size()));
*lines = gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
if (lines_loose) {
*lines_loose = gpu::IndexBufPtr(GPU_indexbuf_create_subrange(
lines->get(), visible_non_loose_edges.size() * 2, visible_loose_edges.size() * 2));
}
}
void extract_lines(const MeshRenderData &mr,
gpu::IndexBufPtr *lines,
gpu::IndexBufPtr *lines_loose,
bool &no_loose_wire)
{
if (mr.extract_type == MeshExtractType::Mesh) {
extract_lines_mesh(mr, lines, lines_loose, no_loose_wire);
}
else {
extract_lines_bm(mr, lines, lines_loose, no_loose_wire);
}
}
static void extract_lines_loose_geom_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
const int edge_loose_offset,
gpu::IndexBuf *ibo)
{
Array<int, 64> loose_edges(mr.loose_edges.size());
mr.loose_edges.to_indices(loose_edges.as_mutable_span());
if (loose_edges.is_empty()) {
return;
}
const int edges_per_edge = subdiv_edges_per_coarse_edge(subdiv_cache);
const int loose_edges_num = subdiv_loose_edges_num(mr, subdiv_cache);
/* Update flags for loose edges, points are already handled. */
static const GPUVertFormat format = GPU_vertformat_from_attribute("data",
gpu::VertAttrType::UINT_32);
gpu::VertBuf *flags = GPU_vertbuf_calloc();
GPU_vertbuf_init_with_format(*flags, format);
GPU_vertbuf_data_alloc(*flags, loose_edges_num);
MutableSpan<uint> flags_data = flags->data<uint>();
switch (mr.extract_type) {
case MeshExtractType::Mesh: {
const int *orig_index_edge = (mr.hide_unmapped_edges) ? mr.orig_index_edge : nullptr;
if (orig_index_edge == nullptr) {
const Span<bool> hide_edge = mr.hide_edge;
if (!hide_edge.is_empty()) {
for (const int i : loose_edges.index_range()) {
const bool value = hide_edge[loose_edges[i]];
flags_data.slice(i * edges_per_edge, edges_per_edge).fill(value);
}
}
else {
flags_data.fill(0);
}
}
else {
if (mr.bm) {
for (const int i : loose_edges.index_range()) {
const BMEdge *bm_edge = bm_original_edge_get(mr, loose_edges[i]);
const int value = (bm_edge) ? BM_elem_flag_test_bool(bm_edge, BM_ELEM_HIDDEN) : true;
flags_data.slice(i * edges_per_edge, edges_per_edge).fill(value);
}
}
else {
const Span<bool> hide_edge = mr.hide_edge;
if (!hide_edge.is_empty()) {
for (const int i : loose_edges.index_range()) {
const bool value = (orig_index_edge[loose_edges[i]] == ORIGINDEX_NONE) ?
false :
hide_edge[loose_edges[i]];
flags_data.slice(i * edges_per_edge, edges_per_edge).fill(value);
}
}
else {
flags_data.fill(0);
}
}
}
break;
}
case MeshExtractType::BMesh: {
BMesh *bm = mr.bm;
for (const int i : loose_edges.index_range()) {
const BMEdge *bm_edge = BM_edge_at_index(bm, loose_edges[i]);
const bool value = BM_elem_flag_test_bool(bm_edge, BM_ELEM_HIDDEN);
flags_data.slice(i * edges_per_edge, edges_per_edge).fill(value);
}
break;
}
}
draw_subdiv_build_lines_loose_buffer(
subdiv_cache, ibo, flags, uint(edge_loose_offset), loose_edges_num);
GPU_vertbuf_discard(flags);
}
void extract_lines_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::IndexBufPtr *lines,
gpu::IndexBufPtr *lines_loose,
bool &no_loose_wire)
{
const int loose_ibo_size = subdiv_loose_edges_num(mr, subdiv_cache) * 2;
no_loose_wire = loose_ibo_size == 0;
if (lines_loose && !lines) {
*lines_loose = gpu::IndexBufPtr(GPU_indexbuf_build_on_device(loose_ibo_size));
extract_lines_loose_geom_subdiv(subdiv_cache, mr, 0, lines_loose->get());
return;
}
const int non_loose_ibo_size = subdiv_cache.num_subdiv_loops * 2;
*lines = gpu::IndexBufPtr(GPU_indexbuf_build_on_device(non_loose_ibo_size + loose_ibo_size));
if (non_loose_ibo_size > 0) {
draw_subdiv_build_lines_buffer(subdiv_cache, lines->get());
}
extract_lines_loose_geom_subdiv(subdiv_cache, mr, non_loose_ibo_size, lines->get());
if (lines_loose) {
/* Multiply by 2 because these are edges indices. */
*lines_loose = gpu::IndexBufPtr(
GPU_indexbuf_create_subrange(lines->get(), non_loose_ibo_size, loose_ibo_size));
}
}
} // namespace blender::draw

View File

@@ -0,0 +1,269 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include <climits>
#include "BLI_map.hh"
#include "BLI_ordered_edge.hh"
#include "BKE_editmesh.hh"
#include "GPU_index_buffer.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
namespace blender::draw {
/** Set when the edge has not yet been initialized. */
#define EDGE_IS_UNSET INT_MAX
/**
* Set when the edge has been initialized and handled.
* This is set instead of #EDGE_IS_UNSET so the edge isn't handled again
* which can write past the buffer bounds, see: #150841.
*
* \note this isn't ideal as the winding checks may match
* or not depending on the order triangles are handled.
* Typically we try to avoid different behavior based on the order of data
* however enforcing exactly matching behavior seems fairly involved without
* much benefit, so accept this shortcoming.
*/
#define EDGE_IS_HANDLED (INT_MAX - 1)
#define EDGE_IS_UNSET_OR_HANDLED(i) ((i) >= EDGE_IS_HANDLED)
static void create_lines_for_remaining_edges(MutableSpan<int> vert_to_corner,
Map<OrderedEdge, int> &edge_hash,
GPUIndexBufBuilder &elb,
bool &is_manifold)
{
for (const auto item : edge_hash.items()) {
int v_data = item.value;
if (EDGE_IS_UNSET_OR_HANDLED(v_data)) {
continue;
}
int v2 = item.key.v_low;
int v3 = item.key.v_high;
int l1 = uint(abs(v_data)) - 1;
if (v_data < 0) { /* `inv_opposite`. */
std::swap(v2, v3);
}
int l2 = vert_to_corner[v2];
int l3 = vert_to_corner[v3];
GPU_indexbuf_add_line_adj_verts(&elb, l1, l2, l3, l1);
is_manifold = false;
}
}
inline void rotate_vector(uint3 &value)
{
const uint tmp = value[0];
value[0] = value[2];
value[2] = value[1];
value[1] = tmp;
}
/** \param vert_to_corner: Array to convert vert index to any corner of this vert. */
inline void lines_adjacency_triangle(uint3 vert_tri,
uint3 corner_tri,
MutableSpan<int> vert_to_corner,
Map<OrderedEdge, int> &edge_hash,
GPUIndexBufBuilder &elb,
bool &is_manifold)
{
/* Iterate around the triangle's edges. */
for (int e = 0; e < 3; e++) {
rotate_vector(vert_tri);
rotate_vector(corner_tri);
bool inv_indices = (vert_tri[1] > vert_tri[2]);
edge_hash.add_or_modify(
{vert_tri[1], vert_tri[2]},
[&](int *value) {
int new_value = int(corner_tri[0]) + 1; /* 0 cannot be signed so add one. */
*value = inv_indices ? -new_value : new_value;
/* Store loop indices for remaining non-manifold edges. */
vert_to_corner[vert_tri[1]] = corner_tri[1];
vert_to_corner[vert_tri[2]] = corner_tri[2];
},
[&](int *value) {
int v_data = *value;
if (v_data == EDGE_IS_UNSET) {
int new_value = int(corner_tri[0]) + 1; /* 0 cannot be signed so add one. */
*value = inv_indices ? -new_value : new_value;
/* Store loop indices for remaining non-manifold edges. */
vert_to_corner[vert_tri[1]] = corner_tri[1];
vert_to_corner[vert_tri[2]] = corner_tri[2];
}
else if (UNLIKELY(v_data == EDGE_IS_HANDLED)) {
/* Ignore additional faces once this has been handled
* as this may exceed the pre-sized index buffer: see #150841 & !151084 for details. */
/* When there are 3+ users of an edge the mesh is not manifold (by definition). */
is_manifold = false;
}
else {
/* HACK Tag as not used.
* Prevent overhead of removing from the hash as well as potentially adding
* the value back in the case more than 2 triangles use this edge. */
*value = EDGE_IS_HANDLED;
bool inv_opposite = (v_data < 0);
const int corner_opposite = abs(v_data) - 1;
/* TODO: Make this part thread-safe. */
if (inv_opposite == inv_indices) {
/* Don't share edge if triangles have non matching winding. */
GPU_indexbuf_add_line_adj_verts(
&elb, corner_tri[0], corner_tri[1], corner_tri[2], corner_tri[0]);
GPU_indexbuf_add_line_adj_verts(
&elb, corner_opposite, corner_tri[1], corner_tri[2], corner_opposite);
is_manifold = false;
}
else {
GPU_indexbuf_add_line_adj_verts(
&elb, corner_tri[0], corner_tri[1], corner_tri[2], corner_opposite);
}
}
});
}
}
static void calc_adjacency_bm(const MeshRenderData &mr,
MutableSpan<int> vert_to_corner,
Map<OrderedEdge, int> &edge_hash,
GPUIndexBufBuilder &elb,
bool &is_manifold)
{
const Span<std::array<BMLoop *, 3>> looptris = mr.edit_bmesh->looptris;
for (const int i : looptris.index_range()) {
const std::array<BMLoop *, 3> &tri = looptris[i];
if (BM_elem_flag_test(tri[0]->f, BM_ELEM_HIDDEN)) {
continue;
}
lines_adjacency_triangle(
uint3(BM_elem_index_get(tri[0]->v),
BM_elem_index_get(tri[1]->v),
BM_elem_index_get(tri[2]->v)),
uint3(BM_elem_index_get(tri[0]), BM_elem_index_get(tri[1]), BM_elem_index_get(tri[2])),
vert_to_corner,
edge_hash,
elb,
is_manifold);
}
}
static void calc_adjacency_mesh(const MeshRenderData &mr,
MutableSpan<int> vert_to_corner,
Map<OrderedEdge, int> &edge_hash,
GPUIndexBufBuilder &elb,
bool &is_manifold)
{
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Span<bool> hide_poly = mr.hide_poly;
const Span<int3> corner_tris = mr.mesh->corner_tris();
for (const int face : faces.index_range()) {
if (!hide_poly.is_empty() && hide_poly[face]) {
continue;
}
const IndexRange tris = bke::mesh::face_triangles_range(faces, face);
for (const int3 &tri : corner_tris.slice(tris)) {
lines_adjacency_triangle(
uint3(corner_verts[tri[0]], corner_verts[tri[1]], corner_verts[tri[2]]),
uint3(tri),
vert_to_corner,
edge_hash,
elb,
is_manifold);
}
}
}
gpu::IndexBufPtr extract_lines_adjacency(const MeshRenderData &mr, bool &r_is_manifold)
{
/* Similar to poly_to_tri_count().
* There is always (loop + triangle - 1) edges inside a face.
* Accumulate for all faces and you get : */
const int tess_edge_len = mr.corners_num + mr.corner_tris_num - mr.faces_num;
Map<OrderedEdge, int> edge_hash;
edge_hash.reserve(tess_edge_len);
Array<int> vert_to_corner(mr.verts_num, 0);
bool is_manifold = true;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES_ADJ, tess_edge_len, mr.corners_num);
if (mr.extract_type == MeshExtractType::Mesh) {
calc_adjacency_mesh(mr, vert_to_corner, edge_hash, builder, is_manifold);
}
else {
calc_adjacency_bm(mr, vert_to_corner, edge_hash, builder, is_manifold);
}
create_lines_for_remaining_edges(vert_to_corner, edge_hash, builder, is_manifold);
r_is_manifold = is_manifold;
return gpu::IndexBufPtr(GPU_indexbuf_build(&builder));
}
gpu::IndexBufPtr extract_lines_adjacency_subdiv(const DRWSubdivCache &subdiv_cache,
bool &r_is_manifold)
{
/* For each face there is (loop + triangle - 1) edges. Since we only have quads, and a quad
* is split into 2 triangles, we have (loop + 2 - 1) = (loop + 1) edges for each quad, or in
* total: (number_of_loops + number_of_quads). */
const uint tess_edge_len = subdiv_cache.num_subdiv_loops + subdiv_cache.num_subdiv_quads;
Array<int> vert_to_corner(subdiv_cache.num_subdiv_verts, 0);
Map<OrderedEdge, int> edge_hash;
edge_hash.reserve(tess_edge_len);
bool is_manifold = true;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES_ADJ, tess_edge_len, subdiv_cache.num_subdiv_loops);
for (const int subdiv_quad_index : IndexRange(subdiv_cache.num_subdiv_quads)) {
const uint loop_index = subdiv_quad_index * 4;
const uint corner_0 = loop_index + 0;
const uint corner_1 = loop_index + 1;
const uint corner_2 = loop_index + 2;
const uint corner_3 = loop_index + 3;
const uint vert_0 = subdiv_cache.subdiv_loop_subdiv_vert_index[corner_0];
const uint vert_1 = subdiv_cache.subdiv_loop_subdiv_vert_index[corner_1];
const uint vert_2 = subdiv_cache.subdiv_loop_subdiv_vert_index[corner_2];
const uint vert_3 = subdiv_cache.subdiv_loop_subdiv_vert_index[corner_3];
lines_adjacency_triangle({vert_0, vert_1, vert_2},
{corner_0, corner_1, corner_2},
vert_to_corner,
edge_hash,
builder,
is_manifold);
lines_adjacency_triangle({vert_0, vert_2, vert_3},
{corner_0, corner_2, corner_3},
vert_to_corner,
edge_hash,
builder,
is_manifold);
}
create_lines_for_remaining_edges(vert_to_corner, edge_hash, builder, is_manifold);
r_is_manifold = is_manifold;
return gpu::IndexBufPtr(GPU_indexbuf_build(&builder));
}
#undef EDGE_IS_UNSET
#undef EDGE_IS_HANDLED
#undef EDGE_IS_CHECK
} // namespace blender::draw

View File

@@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_bitmap.h"
#include "atomic_ops.h"
#include "MEM_guardedalloc.h"
#include "GPU_index_buffer.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
namespace blender::draw {
gpu::IndexBufPtr extract_lines_paint_mask(const MeshRenderData &mr)
{
const OffsetIndices faces = mr.faces;
const Span<int> corner_edges = mr.corner_edges;
const Span<bool> hide_edge = mr.hide_edge;
const Span<bool> select_poly = mr.select_poly;
const Span<int> orig_index_edge = mr.orig_index_edge ?
Span<int>(mr.orig_index_edge, mr.edges_num) :
Span<int>();
GPUIndexBufBuilder builder;
const int max_index = mr.corners_num;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, mr.edges_num, max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
BLI_bitmap *select_map = BLI_BITMAP_NEW(mr.edges_num, __func__);
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
for (const int corner : face) {
const int edge = corner_edges[corner];
if ((!hide_edge.is_empty() && hide_edge[edge]) ||
(!orig_index_edge.is_empty() && (orig_index_edge[edge] == ORIGINDEX_NONE)))
{
data[edge] = uint2(gpu::RESTART_INDEX);
continue;
}
const int corner_next = bke::mesh::face_corner_next(face, corner);
if (!select_poly.is_empty() && select_poly[face_index]) {
if (BLI_BITMAP_TEST_AND_SET_ATOMIC(select_map, edge)) {
/* Hide edge as it has more than 2 selected loop. */
data[edge] = uint2(gpu::RESTART_INDEX);
}
else {
/* First selected loop. Set edge visible, overwriting any unselected loop. */
data[edge] = uint2(corner, corner_next);
}
}
else {
/* Set these unselected loop only if this edge has no other selected loop. */
if (!BLI_BITMAP_TEST(select_map, edge)) {
data[edge] = uint2(corner, corner_next);
}
}
}
}
});
MEM_delete(select_map);
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, true));
}
gpu::IndexBufPtr extract_lines_paint_mask_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
const Span<bool> hide_edge = mr.hide_edge;
const Span<bool> select_poly = mr.select_poly;
const Span<int> orig_index_edge = mr.orig_index_edge ?
Span<int>(mr.orig_index_edge, mr.edges_num) :
Span<int>();
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_cache.num_subdiv_loops);
const Span<int> subdiv_loop_subdiv_edge_index(subdiv_cache.subdiv_loop_subdiv_edge_index,
subdiv_cache.num_subdiv_loops);
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
GPUIndexBufBuilder builder;
const int max_index = subdiv_cache.num_subdiv_loops;
GPU_indexbuf_init(&builder, GPU_PRIM_LINES, subdiv_cache.num_subdiv_edges, max_index);
MutableSpan<uint2> data = GPU_indexbuf_get_data(&builder).cast<uint2>();
BLI_bitmap *select_map = BLI_BITMAP_NEW(mr.edges_num, __func__);
const int quads_num = subdiv_cache.num_subdiv_quads;
threading::parallel_for(IndexRange(quads_num), 4096, [&](const IndexRange range) {
for (const int subdiv_quad_index : range) {
const int coarse_quad_index = subdiv_loop_face_index[subdiv_quad_index * 4];
const IndexRange subdiv_face(subdiv_quad_index * 4, 4);
for (const int corner : subdiv_face) {
const uint coarse_edge_index = uint(subdiv_loop_edge_index[corner]);
const uint subdiv_edge_index = uint(subdiv_loop_subdiv_edge_index[corner]);
if (coarse_edge_index == -1u) {
data[subdiv_edge_index] = uint2(gpu::RESTART_INDEX);
continue;
}
if ((!hide_edge.is_empty() && hide_edge[coarse_edge_index]) ||
(!orig_index_edge.is_empty() &&
(orig_index_edge[coarse_edge_index] == ORIGINDEX_NONE)))
{
data[subdiv_edge_index] = uint2(gpu::RESTART_INDEX);
continue;
}
const int corner_next = bke::mesh::face_corner_next(subdiv_face, corner);
if (!select_poly.is_empty() && select_poly[coarse_quad_index]) {
if (BLI_BITMAP_TEST_AND_SET_ATOMIC(select_map, coarse_edge_index)) {
/* Hide edge as it has more than 2 selected loop. */
data[subdiv_edge_index] = uint2(gpu::RESTART_INDEX);
}
else {
/* First selected loop. Set edge visible, overwriting any unselected loop. */
data[subdiv_edge_index] = uint2(corner, corner_next);
}
}
else {
/* Set these unselected loop only if this edge has no other selected loop. */
if (!BLI_BITMAP_TEST(select_map, coarse_edge_index)) {
data[subdiv_edge_index] = uint2(corner, corner_next);
}
}
}
}
});
MEM_delete(select_map);
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, true));
}
} // namespace blender::draw

View File

@@ -0,0 +1,330 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "GPU_index_buffer.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
#include "BLI_array_utils.hh"
#include "BLI_timeit.hh"
namespace blender::draw {
static IndexMask calc_vert_visibility_mesh(const MeshRenderData &mr,
const IndexMask &mask,
IndexMaskMemory &memory)
{
IndexMask visible = mask;
if (!mr.hide_vert.is_empty()) {
visible = IndexMask::from_bools_inverse(visible, mr.hide_vert, memory);
}
if (mr.orig_index_vert != nullptr) {
const int *orig_index = mr.orig_index_vert;
static_assert(ORIGINDEX_NONE == -1);
visible = array_utils::indices_non_negative(visible, Span(orig_index, mr.verts_num), memory);
}
return visible;
}
/**
* Fill the index buffer in a parallel non-deterministic fashion. This is okay because any of the
* possible face corner indices are correct, since they all correspond to the same #Mesh vertex.
* The separate arrays exist as a performance optimization to avoid writing to the VBO.
*/
template<typename Fn>
static void process_ibo_verts_mesh(const MeshRenderData &mr, const Fn &process_vert_fn)
{
const Span<int> corner_verts = mr.corner_verts;
threading::parallel_for(corner_verts.index_range(), 2048, [&](const IndexRange range) {
for (const int corner : range) {
process_vert_fn(corner, corner_verts[corner]);
}
});
const int loose_edges_start = mr.corners_num;
const Span<int2> edges = mr.edges;
mr.loose_edges.foreach_index_optimized<int>(
[&](const int i, const int pos) {
const int2 edge = edges[i];
process_vert_fn(loose_edges_start + pos * 2 + 0, edge[0]);
process_vert_fn(loose_edges_start + pos * 2 + 1, edge[1]);
},
exec_mode::grain_size(8196));
const int loose_verts_start = mr.corners_num + mr.loose_edges.size() * 2;
mr.loose_verts.foreach_index_optimized<int>(
[&](const int i, const int pos) { process_vert_fn(loose_verts_start + pos, i); },
exec_mode::grain_size(8196));
}
static gpu::IndexBufPtr extract_points_mesh(const MeshRenderData &mr)
{
IndexMaskMemory memory;
const IndexMask visible_verts = calc_vert_visibility_mesh(mr, IndexMask(mr.verts_num), memory);
const int max_index = mr.corners_num + mr.loose_edges.size() * 2 + mr.loose_verts.size();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, visible_verts.size(), max_index);
MutableSpan<uint> data = GPU_indexbuf_get_data(&builder);
threading::memory_bandwidth_bound_task(mr.corner_verts.size_in_bytes(), [&]() {
if (visible_verts.size() == mr.verts_num) {
Array<bool> used(mr.verts_num, false);
process_ibo_verts_mesh(mr, [&](const int ibo_index, const int vert) {
if (!used[vert]) {
used[vert] = true;
data[vert] = ibo_index;
}
});
}
else {
/* Compress the vertex indices into the smaller range of visible vertices in the IBO. */
Array<int> map(mr.verts_num, -1);
index_mask::build_reverse_map(visible_verts, map.as_mutable_span());
process_ibo_verts_mesh(mr, [&](const int ibo_index, const int vert) {
const int index = map[vert];
if (index != -1) {
map[vert] = -1;
data[index] = ibo_index;
}
});
}
});
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
}
template<typename Fn>
static void process_ibo_verts_bm(const MeshRenderData &mr, const Fn &process_vert_fn)
{
BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(mr.verts_num), 4096, [&](const IndexRange range) {
for (const int i : range) {
BMVert &vert = *BM_vert_at_index(&bm, i);
if (const BMLoop *loop = BM_vert_find_first_loop(&vert)) {
process_vert_fn(BM_elem_index_get(loop), i);
}
}
});
const int loose_edges_start = mr.corners_num;
mr.loose_edges.foreach_index(
[&](const int i, const int pos) {
const BMEdge &edge = *BM_edge_at_index(&bm, i);
process_vert_fn(loose_edges_start + pos * 2 + 0, BM_elem_index_get(edge.v1));
process_vert_fn(loose_edges_start + pos * 2 + 1, BM_elem_index_get(edge.v2));
},
exec_mode::grain_size(4096));
const int loose_verts_start = mr.corners_num + mr.loose_edges.size() * 2;
mr.loose_verts.foreach_index_optimized<int>(
[&](const int i, const int pos) { process_vert_fn(loose_verts_start + pos, i); },
exec_mode::grain_size(4096));
}
static gpu::IndexBufPtr extract_points_bm(const MeshRenderData &mr)
{
BMesh &bm = *mr.bm;
IndexMaskMemory memory;
const IndexMask visible_verts = IndexMask::from_predicate(
IndexRange(bm.totvert), memory, [&](const int i) {
return !BM_elem_flag_test_bool(BM_vert_at_index(&const_cast<BMesh &>(bm), i),
BM_ELEM_HIDDEN);
});
const int max_index = mr.corners_num + mr.loose_edges.size() * 2 + mr.loose_verts.size();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_POINTS, visible_verts.size(), max_index);
MutableSpan<uint> data = GPU_indexbuf_get_data(&builder);
if (mr.loose_verts.is_empty() && mr.loose_edges.is_empty()) {
/* Make use of BMesh's vertex to loop topology knowledge to iterate over verts instead of
* iterating over faces and defining points implicitly as done in the #Mesh extraction. */
visible_verts.foreach_index(
[&](const int i, const int pos) {
BMVert &vert = *BM_vert_at_index(&bm, i);
data[pos] = BM_elem_index_get(BM_vert_find_first_loop(&vert));
},
exec_mode::grain_size(4096));
}
else if (visible_verts.size() == bm.totvert) {
Array<bool> used(mr.verts_num, false);
process_ibo_verts_bm(mr, [&](const int ibo_index, const int vert) {
if (!used[vert]) {
used[vert] = true;
data[vert] = ibo_index;
}
});
}
else {
/* Compress the vertex indices into the smaller range of visible vertices in the IBO. */
Array<int> map(mr.verts_num, -1);
index_mask::build_reverse_map(visible_verts, map.as_mutable_span());
process_ibo_verts_bm(mr, [&](const int ibo_index, const int vert) {
const int index = map[vert];
if (index != -1) {
map[vert] = -1;
data[index] = ibo_index;
}
});
}
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, false));
}
gpu::IndexBufPtr extract_points(const MeshRenderData &mr)
{
if (mr.extract_type == MeshExtractType::Mesh) {
return extract_points_mesh(mr);
}
return extract_points_bm(mr);
}
static IndexMask calc_vert_visibility_mapped_mesh(const MeshRenderData &mr,
const IndexMask &mask,
const Span<int> map,
IndexMaskMemory &memory)
{
IndexMask visible = mask;
if (!mr.hide_vert.is_empty()) {
const Span<bool> hide_vert = mr.hide_vert;
visible = IndexMask::from_predicate(
visible, memory, [&](const int i) { return !hide_vert[map[i]]; });
}
if (mr.orig_index_vert != nullptr) {
const int *orig_index = mr.orig_index_vert;
visible = IndexMask::from_predicate(
visible, memory, [&](const int i) { return orig_index[map[i]] != ORIGINDEX_NONE; });
}
return visible;
}
static gpu::IndexBufPtr extract_points_subdiv_mesh(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
const Span<int2> coarse_edges = mr.edges;
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
const int loose_edge_verts_num = verts_per_edge * mr.loose_edges.size();
const Span<bool> hide_vert = mr.hide_vert;
const Span<int> corner_orig_verts = subdiv_cache.verts_orig_index->data<int>();
IndexMaskMemory memory;
IndexMask visible_corners = IndexMask::from_predicate(
corner_orig_verts.index_range(), memory, [&](const int i) {
return corner_orig_verts[i] != -1;
});
visible_corners = calc_vert_visibility_mapped_mesh(
mr, visible_corners, corner_orig_verts, memory);
Array<int, 64> loose_verts(mr.loose_verts.size());
mr.loose_verts.to_indices(loose_verts.as_mutable_span());
const IndexMask visible_loose = calc_vert_visibility_mapped_mesh(
mr, IndexMask(loose_verts.size()), loose_verts, memory);
const int max_index = subdiv_cache.num_subdiv_loops + loose_edge_verts_num + loose_verts.size();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder,
GPU_PRIM_POINTS,
visible_corners.size() + mr.loose_edges.size() * 2 + visible_loose.size(),
max_index);
MutableSpan<uint> data = GPU_indexbuf_get_data(&builder);
visible_corners.to_indices<int32_t>(data.take_front(visible_corners.size()).cast<int32_t>());
const auto show_vert = [&](const int vert) {
if (!hide_vert.is_empty() && hide_vert[vert]) {
return false;
}
if (mr.orig_index_vert && mr.orig_index_vert[vert] == ORIGINDEX_NONE) {
return false;
}
return true;
};
MutableSpan loose_edge_data = data.slice(visible_corners.size(), mr.loose_edges.size() * 2);
const int loose_geom_start = subdiv_cache.num_subdiv_loops;
mr.loose_edges.foreach_index([&](const int i, const int pos) {
const int2 edge = coarse_edges[i];
const IndexRange edge_range(loose_geom_start + pos * verts_per_edge, verts_per_edge);
loose_edge_data[pos * 2 + 0] = show_vert(edge[0]) ? edge_range.first() : gpu::RESTART_INDEX;
loose_edge_data[pos * 2 + 1] = show_vert(edge[1]) ? edge_range.last() : gpu::RESTART_INDEX;
});
MutableSpan loose_vert_data = data.take_back(visible_loose.size()).cast<int32_t>();
const int loose_verts_start = loose_geom_start + loose_edge_verts_num;
visible_loose.shift(loose_verts_start, memory).to_indices<int32_t>(loose_vert_data);
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, true));
}
static gpu::IndexBufPtr extract_points_subdiv_bm(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
const Span<int2> coarse_edges = mr.edges;
const IndexMask &loose_verts = mr.loose_verts;
const IndexMask &loose_edges = mr.loose_edges;
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
const int loose_edge_verts_num = verts_per_edge * loose_edges.size();
const Span<int> corner_orig_verts = subdiv_cache.verts_orig_index->data<int>();
const auto show_vert_bm = [&](const int vert_index) {
const BMVert *vert = mr.orig_index_vert ? bm_original_vert_get(mr, vert_index) :
BM_vert_at_index(mr.bm, vert_index);
return !BM_elem_flag_test_bool(vert, BM_ELEM_HIDDEN);
};
IndexMaskMemory memory;
const IndexMask visible_corners = IndexMask::from_predicate(
corner_orig_verts.index_range(), memory, [&](const int i) {
return corner_orig_verts[i] != -1 && show_vert_bm(corner_orig_verts[i]);
});
const IndexMask visible_loose = IndexMask::from_predicate(
loose_verts.index_range(), memory, [&](const int vert) { return show_vert_bm(vert); });
const int max_index = subdiv_cache.num_subdiv_loops + loose_edge_verts_num + loose_verts.size();
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder,
GPU_PRIM_POINTS,
visible_corners.size() + loose_edges.size() * 2 + visible_loose.size(),
max_index);
MutableSpan<uint> data = GPU_indexbuf_get_data(&builder);
visible_corners.to_indices<int32_t>(data.take_front(visible_corners.size()).cast<int32_t>());
MutableSpan loose_edge_data = data.slice(visible_corners.size(), loose_edges.size() * 2);
const int loose_geom_start = subdiv_cache.num_subdiv_loops;
for (const int i : loose_edges.index_range()) {
const int2 edge = coarse_edges[loose_edges[i]];
const IndexRange edge_range(loose_geom_start + i * verts_per_edge, verts_per_edge);
loose_edge_data[i * 2 + 0] = show_vert_bm(edge[0]) ? edge_range.first() : gpu::RESTART_INDEX;
loose_edge_data[i * 2 + 1] = show_vert_bm(edge[1]) ? edge_range.last() : gpu::RESTART_INDEX;
}
MutableSpan loose_vert_data = data.take_back(visible_loose.size()).cast<int32_t>();
const int loose_verts_start = loose_geom_start + loose_edge_verts_num;
visible_loose.shift(loose_verts_start, memory).to_indices<int32_t>(loose_vert_data);
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, max_index, true));
}
gpu::IndexBufPtr extract_points_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
if (mr.extract_type == MeshExtractType::Mesh) {
return extract_points_subdiv_mesh(mr, subdiv_cache);
}
return extract_points_subdiv_bm(mr, subdiv_cache);
}
} // namespace blender::draw

View File

@@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_editmesh.hh"
#include "BLI_math_geom.h"
#include "GPU_index_buffer.hh"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static gpu::IndexBufPtr extract_tris_mesh(const MeshRenderData &mr,
const SortedFaceData &face_sorted)
{
const Span<int3> corner_tris = mr.mesh->corner_tris();
if (!face_sorted.face_tri_offsets) {
/* There are no hidden faces and no reordering is necessary to group triangles with the same
* material. The corner indices from #Mesh::corner_tris() can be copied directly to the GPU. */
BLI_assert(face_sorted.visible_tris_num == corner_tris.size());
return gpu::IndexBufPtr(GPU_indexbuf_build_from_memory(GPU_PRIM_TRIS,
corner_tris.cast<uint32_t>().data(),
corner_tris.size(),
0,
mr.corners_num,
false));
}
const OffsetIndices faces = mr.faces;
const Span<bool> hide_poly = mr.hide_poly;
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_TRIS, face_sorted.visible_tris_num, mr.corners_num);
MutableSpan<uint3> data = GPU_indexbuf_get_data(&builder).cast<uint3>();
const Span<int> face_tri_offsets = face_sorted.face_tri_offsets->as_span();
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face : range) {
if (!hide_poly.is_empty() && hide_poly[face]) {
continue;
}
const IndexRange mesh_range = bke::mesh::face_triangles_range(faces, face);
const Span<uint3> mesh_tris = corner_tris.slice(mesh_range).cast<uint3>();
MutableSpan<uint3> ibo_tris = data.slice(face_tri_offsets[face], mesh_tris.size());
ibo_tris.copy_from(mesh_tris);
}
});
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, mr.corners_num, false));
}
static gpu::IndexBufPtr extract_tris_bmesh(const MeshRenderData &mr,
const SortedFaceData &face_sorted)
{
GPUIndexBufBuilder builder;
GPU_indexbuf_init(&builder, GPU_PRIM_TRIS, face_sorted.visible_tris_num, mr.corners_num);
MutableSpan<uint3> data = GPU_indexbuf_get_data(&builder).cast<uint3>();
BMesh &bm = *mr.bm;
const Span<std::array<BMLoop *, 3>> looptris = mr.edit_bmesh->looptris;
const Span<int> face_tri_offsets = *face_sorted.face_tri_offsets;
threading::parallel_for(IndexRange(bm.totface), 1024, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&bm, face_index);
if (BM_elem_flag_test(&face, BM_ELEM_HIDDEN)) {
continue;
}
const int corner_index = BM_elem_index_get(BM_FACE_FIRST_LOOP(&face));
const IndexRange bm_tris(poly_to_tri_count(face_index, corner_index),
bke::mesh::face_triangles_num(face.len));
const IndexRange ibo_tris(face_tri_offsets[face_index], bm_tris.size());
for (const int i : bm_tris.index_range()) {
data[ibo_tris[i]] = uint3(BM_elem_index_get(looptris[bm_tris[i]][0]),
BM_elem_index_get(looptris[bm_tris[i]][1]),
BM_elem_index_get(looptris[bm_tris[i]][2]));
}
}
});
return gpu::IndexBufPtr(GPU_indexbuf_build_ex(&builder, 0, bm.totloop, false));
}
void create_material_subranges(const SortedFaceData &face_sorted,
gpu::IndexBuf &tris_ibo,
MutableSpan<gpu::IndexBufPtr> ibos)
{
/* Create ibo sub-ranges. Always do this to avoid error when the standard surface batch
* is created before the surfaces-per-material. */
int mat_start = 0;
for (const int i : face_sorted.tris_num_by_material.index_range()) {
/* These IBOs have not been queried yet but we create them just in case they are needed
* later since they are not tracked by mesh_buffer_cache_create_requested(). */
const int mat_tri_len = face_sorted.tris_num_by_material[i];
/* Multiply by 3 because these are triangle indices. */
const int start = mat_start * 3;
const int len = mat_tri_len * 3;
ibos[i] = gpu::IndexBufPtr(GPU_indexbuf_create_subrange(&tris_ibo, start, len));
mat_start += mat_tri_len;
}
}
gpu::IndexBufPtr extract_tris(const MeshRenderData &mr, const SortedFaceData &face_sorted)
{
if (mr.extract_type == MeshExtractType::Mesh) {
return extract_tris_mesh(mr, face_sorted);
}
return extract_tris_bmesh(mr, face_sorted);
}
gpu::IndexBufPtr extract_tris_subdiv(const DRWSubdivCache &subdiv_cache, MeshBatchCache &cache)
{
/* Initialize the index buffer, it was already allocated, it will be filled on the device. */
gpu::IndexBufPtr ibo = gpu::IndexBufPtr(
GPU_indexbuf_build_on_device(subdiv_cache.num_subdiv_triangles * 3));
if (!cache.tris_per_mat.is_empty()) {
for (int i = 0; i < cache.mat_len; i++) {
/* Multiply by 6 since we have 2 triangles per quad. */
const int start = subdiv_cache.mat_start[i] * 6;
const int len = (subdiv_cache.mat_end[i] - subdiv_cache.mat_start[i]) * 6;
cache.tris_per_mat[i] = gpu::IndexBufPtr(
GPU_indexbuf_create_subrange(ibo.get(), start, len));
}
}
draw_subdiv_build_tris_buffer(subdiv_cache, ibo.get(), cache.mat_len);
return ibo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,341 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "BLI_string.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_math.hh"
#include "BKE_mesh.hh"
#include "attribute_convert.hh"
#include "draw_attributes.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
#include "GPU_vertex_buffer.hh"
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Extract Attributes
* \{ */
static void init_vbo_for_attribute(const MeshRenderData &mr,
gpu::VertBuf &vbo,
const StringRef name,
const bke::AttrType type,
bool build_on_device,
uint32_t len)
{
char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
/* Attributes use auto-name. */
SNPRINTF(attr_name, "a%s", attr_safe_name);
GPUVertFormat format = init_format_for_attribute(type, attr_name);
GPU_vertformat_deinterleave(&format);
if (mr.active_color_name && name == mr.active_color_name) {
GPU_vertformat_alias_add(&format, "ac");
}
if (mr.default_color_name && name == mr.default_color_name) {
GPU_vertformat_alias_add(&format, "c");
}
if (build_on_device) {
GPU_vertbuf_init_build_on_device(vbo, format, len);
}
else {
GPU_vertbuf_init_with_format(vbo, format);
GPU_vertbuf_data_alloc(vbo, len);
}
}
template<typename T>
static void extract_data_mesh_mapped_corner(const Span<T> attribute,
const Span<int> indices,
gpu::VertBuf &vbo)
{
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
MutableSpan data = vbo.data<VBOType>();
if constexpr (std::is_same_v<T, VBOType>) {
array_utils::gather(attribute, indices, data);
}
else {
threading::parallel_for(indices.index_range(), 8192, [&](const IndexRange range) {
for (const int i : range) {
data[i] = Converter::convert(attribute[indices[i]]);
}
});
}
}
template<typename T>
static void extract_data_mesh_face(const OffsetIndices<int> faces,
const Span<T> attribute,
gpu::VertBuf &vbo)
{
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
MutableSpan data = vbo.data<VBOType>();
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int i : range) {
data.slice(faces[i]).fill(Converter::convert(attribute[i]));
}
});
}
template<typename T>
static void extract_data_bmesh_vert(const BMesh &bm, const int cd_offset, gpu::VertBuf &vbo)
{
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
VBOType *data = vbo.data<VBOType>().data();
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, &const_cast<BMesh &>(bm), BM_FACES_OF_MESH) {
const BMLoop *loop = BM_FACE_FIRST_LOOP(face);
for ([[maybe_unused]] const int i : IndexRange(face->len)) {
const T *src = static_cast<const T *>(POINTER_OFFSET(loop->v->head.data, cd_offset));
*data = Converter::convert(*src);
loop = loop->next;
data++;
}
}
}
template<typename T>
static void extract_data_bmesh_edge(const BMesh &bm, const int cd_offset, gpu::VertBuf &vbo)
{
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
VBOType *data = vbo.data<VBOType>().data();
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, &const_cast<BMesh &>(bm), BM_FACES_OF_MESH) {
const BMLoop *loop = BM_FACE_FIRST_LOOP(face);
for ([[maybe_unused]] const int i : IndexRange(face->len)) {
const T &src = *static_cast<const T *>(POINTER_OFFSET(loop->e->head.data, cd_offset));
*data = Converter::convert(src);
loop = loop->next;
data++;
}
}
}
template<typename T>
static void extract_data_bmesh_face(const BMesh &bm, const int cd_offset, gpu::VertBuf &vbo)
{
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
VBOType *data = vbo.data<VBOType>().data();
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, &const_cast<BMesh &>(bm), BM_FACES_OF_MESH) {
const T &src = *static_cast<const T *>(POINTER_OFFSET(face->head.data, cd_offset));
std::fill_n(data, face->len, Converter::convert(src));
data += face->len;
}
}
template<typename T>
static void extract_data_bmesh_loop(const BMesh &bm, const int cd_offset, gpu::VertBuf &vbo)
{
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
VBOType *data = vbo.data<VBOType>().data();
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, &const_cast<BMesh &>(bm), BM_FACES_OF_MESH) {
const BMLoop *loop = BM_FACE_FIRST_LOOP(face);
for ([[maybe_unused]] const int i : IndexRange(face->len)) {
const T &src = *static_cast<const T *>(POINTER_OFFSET(loop->head.data, cd_offset));
*data = Converter::convert(src);
loop = loop->next;
data++;
}
}
}
static void extract_attribute_data(const MeshRenderData &mr,
const BMDataLayerLookup &attr,
gpu::VertBuf &vbo)
{
bke::attribute_math::to_static_type(attr.type, [&]<typename T>() {
if constexpr (!std::is_void_v<typename AttributeConverter<T>::VBOType>) {
switch (attr.domain) {
case bke::AttrDomain::Point:
extract_data_bmesh_vert<T>(*mr.bm, attr.offset, vbo);
break;
case bke::AttrDomain::Edge:
extract_data_bmesh_edge<T>(*mr.bm, attr.offset, vbo);
break;
case bke::AttrDomain::Face:
extract_data_bmesh_face<T>(*mr.bm, attr.offset, vbo);
break;
case bke::AttrDomain::Corner:
extract_data_bmesh_loop<T>(*mr.bm, attr.offset, vbo);
break;
default:
BLI_assert_unreachable();
}
}
});
}
static void extract_attribute_data(const MeshRenderData &mr,
const bke::GAttributeReader &attr,
gpu::VertBuf &vbo)
{
if (attr.varray.is_single()) {
bke::attribute_math::to_static_type(attr.varray.type(), [&]<typename T>() {
const VArray<T> &src = attr.varray.typed<T>();
if constexpr (!std::is_void_v<typename AttributeConverter<T>::VBOType>) {
using Converter = AttributeConverter<T>;
using VBOType = typename Converter::VBOType;
MutableSpan data = vbo.data<VBOType>();
data.fill(Converter::convert(src.get_internal_single()));
}
});
return;
}
bke::attribute_math::to_static_type(attr.varray.type(), [&]<typename T>() {
if constexpr (!std::is_void_v<typename AttributeConverter<T>::VBOType>) {
switch (attr.domain) {
case bke::AttrDomain::Point:
extract_data_mesh_mapped_corner(GVArraySpan(*attr).typed<T>(), mr.corner_verts, vbo);
break;
case bke::AttrDomain::Edge:
extract_data_mesh_mapped_corner(GVArraySpan(*attr).typed<T>(), mr.corner_edges, vbo);
break;
case bke::AttrDomain::Face:
extract_data_mesh_face(mr.faces, GVArraySpan(*attr).typed<T>(), vbo);
break;
case bke::AttrDomain::Corner:
vertbuf_data_extract_direct(GVArraySpan(*attr).typed<T>(), vbo);
break;
default:
BLI_assert_unreachable();
}
}
});
}
gpu::VertBufPtr extract_attribute(const MeshRenderData &mr, const StringRef name)
{
gpu::VertBuf *vbo = GPU_vertbuf_calloc();
if (mr.extract_type == MeshExtractType::BMesh) {
const BMDataLayerLookup attr = BM_data_layer_lookup(*mr.bm, name);
if (!attr) {
return {};
}
const bke::AttrType type = attr.type;
init_vbo_for_attribute(mr, *vbo, name, type, false, uint32_t(mr.corners_num));
extract_attribute_data(mr, attr, *vbo);
}
else {
const bke::AttributeAccessor attributes = mr.mesh->attributes();
const bke::GAttributeReader attr = attributes.lookup(name);
if (!attr) {
return {};
}
const bke::AttrType type = bke::cpp_type_to_attribute_type(attr.varray.type());
init_vbo_for_attribute(mr, *vbo, name, type, false, uint32_t(mr.corners_num));
extract_attribute_data(mr, attr, *vbo);
}
return gpu::VertBufPtr(vbo);
}
static gpu::VertBufPtr init_coarse_data(const bke::AttrType type, const int coarse_corners_num)
{
gpu::VertBuf *vbo = GPU_vertbuf_calloc();
GPUVertFormat coarse_format = draw::init_format_for_attribute(type, "data");
GPU_vertbuf_init_with_format_ex(*vbo, coarse_format, GPU_USAGE_STATIC);
GPU_vertbuf_data_alloc(*vbo, uint32_t(coarse_corners_num));
return gpu::VertBufPtr(vbo);
}
gpu::VertBufPtr extract_attribute_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const StringRef name)
{
const Mesh *coarse_mesh = subdiv_cache.mesh;
/* Prepare VBO for coarse data. The compute shader only expects floats. */
gpu::VertBufPtr coarse_vbo;
bke::AttrType type;
if (mr.extract_type == MeshExtractType::BMesh) {
const BMDataLayerLookup attr = BM_data_layer_lookup(*mr.bm, name);
if (!attr) {
return {};
}
type = attr.type;
coarse_vbo = init_coarse_data(type, coarse_mesh->corners_num);
extract_attribute_data(mr, attr, *coarse_vbo);
}
else {
const bke::AttributeAccessor attributes = mr.mesh->attributes();
const bke::GAttributeReader attr = attributes.lookup(name);
if (!attr) {
return {};
}
type = bke::cpp_type_to_attribute_type(attr.varray.type());
coarse_vbo = init_coarse_data(type, coarse_mesh->corners_num);
extract_attribute_data(mr, attr, *coarse_vbo);
}
gpu::VertBuf *vbo = GPU_vertbuf_calloc();
init_vbo_for_attribute(mr, *vbo, name, type, true, subdiv_cache.num_subdiv_loops);
/* Ensure data is uploaded properly. */
GPU_vertbuf_tag_dirty(coarse_vbo.get());
bke::attribute_math::to_static_type(type, [&]<typename T>() {
using Converter = AttributeConverter<T>;
if constexpr (!std::is_void_v<typename Converter::VBOType>) {
draw_subdiv_interp_custom_data(subdiv_cache,
*coarse_vbo,
*vbo,
Converter::gpu_component_type,
Converter::gpu_component_len,
0);
}
});
return gpu::VertBufPtr(vbo);
}
gpu::VertBufPtr extract_attr_viewer(const MeshRenderData &mr)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"attribute_value", gpu::VertAttrType::SFLOAT_32_32_32_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
MutableSpan vbo_data = vbo->data<ColorGeometry4f>();
const StringRefNull attr_name = ".viewer";
const bke::AttributeAccessor attributes = mr.mesh->attributes();
const bke::AttributeReader attribute = attributes.lookup_or_default<ColorGeometry4f>(
attr_name, bke::AttrDomain::Corner, {1.0f, 0.0f, 1.0f, 1.0f});
attribute.varray.materialize(vbo_data);
return vbo;
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,212 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_math_vector.hh"
#include "GPU_capabilities.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
namespace blender::draw {
struct MEdgeDataPrev {
int corner_a;
/* Data that represents:
* - the index of the face of `corner_a` before the 2nd loop is found
* - the index of the next radial corner after the 2nd loop is found */
int data;
};
/**
* Calculates a factor that is used to identify the minimum angle in the shader to display an edge.
* NOTE: Keep in sync with `subdiv_vbo_edge_fac_comp.glsl`.
*/
inline float edge_factor_calc(const float3 &a, const float3 &b)
{
const float cosine = math::dot(a, b);
/* Re-scale to the slider range. */
float fac = (200 * (cosine - 1.0f)) + 1.0f;
CLAMP(fac, 0.0f, 1.0f);
/* 1.0 is a reserved value to force hide the wire. */
constexpr float factor = 254.0f / 255.0f;
return fac * factor;
}
static void extract_edge_factor_mesh(const MeshRenderData &mr, MutableSpan<float> vbo_data)
{
const OffsetIndices faces = mr.faces;
const Span<int> corner_edges = mr.corner_edges;
const Span<float3> face_normals = mr.face_normals;
const BitSpan optimal_display_edges = mr.mesh->runtime->subsurf_optimal_display_edges;
Array<int8_t> edge_face_count(mr.edges_num, 0);
Array<MEdgeDataPrev> edge_data(mr.edges_num);
for (const int face : faces.index_range()) {
for (const int corner : faces[face]) {
const int edge = corner_edges[corner];
if (!optimal_display_edges.is_empty() && !optimal_display_edges[edge]) {
vbo_data[corner] = 1.0f;
continue;
}
MEdgeDataPrev *medata = &edge_data[edge];
const int8_t face_count = edge_face_count[edge];
vbo_data[corner] = 0;
if (face_count < 4) {
if (face_count == 0) {
/* Prepare to calculate the factor. */
medata->corner_a = corner;
medata->data = face;
}
else if (face_count == 1) {
/* Calculate the factor for both corners. */
const int other_face = medata->data;
const float factor = edge_factor_calc(face_normals[other_face], face_normals[face]);
vbo_data[medata->corner_a] = factor;
vbo_data[corner] = factor;
/* If the count still changes, use this `data` member to inform the corner. */
medata->data = corner;
}
else {
/* Non-manifold edge. Always visible. */
const int corner_a = medata->corner_a;
const int corner_b = medata->data;
vbo_data[corner_a] = 0;
vbo_data[corner_b] = 0;
}
edge_face_count[edge]++;
}
}
}
}
static void extract_edge_factor_bm(const MeshRenderData &mr, MutableSpan<float> vbo_data)
{
BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&bm, face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
if (BM_edge_is_manifold(loop->e)) {
const BMFace *other_face = loop->radial_next->f;
vbo_data[index] = edge_factor_calc(float3(bm_face_no_get(mr, &face)),
float3(bm_face_no_get(mr, other_face)));
}
else {
vbo_data[index] = 0.0f;
}
loop = loop->next;
}
}
});
}
gpu::VertBufPtr extract_edge_factor(const MeshRenderData &mr)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute("wd",
gpu::VertAttrType::SFLOAT_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num + mr.loose_indices_num);
MutableSpan vbo_data = vbo->data<float>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_edge_factor_mesh(mr, vbo_data);
}
else {
extract_edge_factor_bm(mr, vbo_data);
}
vbo_data.take_back(mr.loose_indices_num).fill(0.0f);
return vbo;
}
static gpu::VertBuf *build_poly_other_map_vbo(const DRWSubdivCache &subdiv_cache)
{
gpu::VertBuf *vbo = GPU_vertbuf_calloc();
static const GPUVertFormat format = GPU_vertformat_from_attribute("poly_other",
gpu::VertAttrType::SINT_32);
GPU_vertbuf_init_with_format(*vbo, format);
GPU_vertbuf_data_alloc(*vbo, subdiv_cache.num_subdiv_loops);
MutableSpan vbo_data = vbo->data<int>();
Array<MEdgeDataPrev> edge_data(subdiv_cache.num_subdiv_edges);
Array<int> tmp_edge_corner_count(subdiv_cache.num_subdiv_edges, 0);
int *subdiv_loop_subdiv_edge_index = subdiv_cache.subdiv_loop_subdiv_edge_index;
for (int corner : IndexRange(subdiv_cache.num_subdiv_loops)) {
const int edge = subdiv_loop_subdiv_edge_index[corner];
const int quad = corner / 4;
const int corner_count = tmp_edge_corner_count[edge]++;
vbo_data[corner] = -1;
if (corner_count == 0) {
edge_data[edge].corner_a = corner;
edge_data[edge].data = quad;
}
else if (corner_count == 1) {
const int corner_a = edge_data[edge].corner_a;
const int quad_a = edge_data[edge].data;
vbo_data[corner_a] = quad;
vbo_data[corner] = quad_a;
edge_data[edge].data = corner;
}
else if (corner_count == 2) {
const int corner_a = edge_data[edge].corner_a;
const int corner_b = edge_data[edge].data;
vbo_data[corner_a] = -1;
vbo_data[corner_b] = -1;
}
}
return vbo;
}
gpu::VertBufPtr extract_edge_factor_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::VertBuf &pos_nor)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_on_device(
GPU_vertformat_from_attribute("wd", gpu::VertAttrType::SFLOAT_32),
subdiv_cache.num_subdiv_loops + subdiv_loose_edges_num(mr, subdiv_cache) * 2));
if (mr.faces_num > 0) {
gpu::VertBuf *poly_other_map = build_poly_other_map_vbo(subdiv_cache);
draw_subdiv_build_edge_fac_buffer(
subdiv_cache, &pos_nor, subdiv_cache.edges_draw_flag, poly_other_map, vbo.get());
GPU_vertbuf_discard(poly_other_map);
}
const int loose_edges_num = subdiv_loose_edges_num(mr, subdiv_cache);
if (loose_edges_num == 0) {
return vbo;
}
/* Make sure buffer is active for sending loose data. */
GPU_vertbuf_use(vbo.get());
const int offset = subdiv_cache.num_subdiv_loops;
const float values[2] = {1.0f, 1.0f};
for (const int i : IndexRange(loose_edges_num)) {
GPU_vertbuf_update_sub(vbo.get(), (offset + i * 2) * sizeof(float), sizeof(values), values);
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,399 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "DNA_meshdata_types.h"
#include "extract_mesh.hh"
#include "draw_cache_impl.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static void mesh_render_data_edge_flag(const MeshRenderData &mr,
const BMEdge *eed,
EditLoopData &eattr)
{
const ToolSettings *ts = mr.toolsettings;
const bool is_vertex_select_mode = (ts != nullptr) && (ts->selectmode & SCE_SELECT_VERTEX) != 0;
const bool is_face_only_select_mode = (ts != nullptr) && (ts->selectmode == SCE_SELECT_FACE);
if (eed == mr.eed_act) {
eattr.e_flag |= VFLAG_EDGE_ACTIVE;
}
if (!is_vertex_select_mode && BM_elem_flag_test(eed, BM_ELEM_SELECT)) {
eattr.e_flag |= VFLAG_EDGE_SELECTED;
}
if (is_vertex_select_mode && BM_elem_flag_test(eed->v1, BM_ELEM_SELECT) &&
BM_elem_flag_test(eed->v2, BM_ELEM_SELECT))
{
eattr.e_flag |= VFLAG_EDGE_SELECTED;
eattr.e_flag |= VFLAG_VERT_SELECTED;
}
if (BM_elem_flag_test(eed, BM_ELEM_SEAM)) {
eattr.e_flag |= VFLAG_EDGE_SEAM;
}
if (!BM_elem_flag_test(eed, BM_ELEM_SMOOTH)) {
eattr.e_flag |= VFLAG_EDGE_SHARP;
}
/* Use active edge color for active face edges because
* specular highlights make it hard to see #55456#510873.
*
* This isn't ideal since it can't be used when mixing edge/face modes
* but it's still better than not being able to see the active face. */
if (is_face_only_select_mode) {
if (mr.efa_act != nullptr) {
if (BM_edge_in_face(eed, mr.efa_act)) {
eattr.e_flag |= VFLAG_EDGE_ACTIVE;
}
}
}
/* Use half a byte for value range */
if (mr.edge_crease_ofs != -1) {
float crease = BM_ELEM_CD_GET_FLOAT(eed, mr.edge_crease_ofs);
if (crease > 0) {
eattr.crease = uchar(ceil(crease * 15.0f));
}
}
/* Use a byte for value range */
if (mr.bweight_ofs != -1) {
float bweight = BM_ELEM_CD_GET_FLOAT(eed, mr.bweight_ofs);
if (bweight > 0) {
eattr.bweight = uchar(bweight * 255.0f);
}
}
#ifdef WITH_FREESTYLE
if (mr.freestyle_edge_ofs != -1) {
if (BM_ELEM_CD_GET_BOOL(eed, mr.freestyle_edge_ofs)) {
eattr.e_flag |= VFLAG_EDGE_FREESTYLE;
}
}
#endif
}
static void mesh_render_data_vert_flag(const MeshRenderData &mr,
const BMVert *eve,
EditLoopData &eattr)
{
if (eve == mr.eve_act) {
eattr.e_flag |= VFLAG_VERT_ACTIVE;
}
if (BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
eattr.e_flag |= VFLAG_VERT_SELECTED;
}
/* Use half a byte for value range */
if (mr.vert_crease_ofs != -1) {
float crease = BM_ELEM_CD_GET_FLOAT(eve, mr.vert_crease_ofs);
if (crease > 0) {
eattr.crease |= uchar(ceil(crease * 15.0f)) << 4;
}
}
}
static const GPUVertFormat &get_edit_data_format()
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
/* WARNING: Adjust #EditLoopData struct accordingly. */
GPU_vertformat_attr_add(&format, "data", gpu::VertAttrType::UINT_8_8_8_8);
GPU_vertformat_alias_add(&format, "flag");
return format;
}();
return format;
}
static void extract_edit_data_mesh(const MeshRenderData &mr, MutableSpan<EditLoopData> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
const BMUVOffsets uv_offsets_none = BMUVOFFSETS_NONE;
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Span<int> corner_edges = mr.corner_edges;
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face : range) {
EditLoopData face_value = {};
if (const BMFace *bm_face = bm_original_face_get(mr, face)) {
mesh_render_data_face_flag(mr, bm_face, uv_offsets_none, face_value);
}
for (const int corner : faces[face]) {
EditLoopData &value = corners_data[corner];
value = face_value;
if (const BMVert *bm_vert = bm_original_vert_get(mr, corner_verts[corner])) {
mesh_render_data_vert_flag(mr, bm_vert, value);
}
if (const BMEdge *bm_edge = bm_original_edge_get(mr, corner_edges[corner])) {
mesh_render_data_edge_flag(mr, bm_edge, value);
}
}
}
});
const Span<int2> edges = mr.edges;
mr.loose_edges.foreach_index(
[&](const int edge_i, const int pos) {
EditLoopData &value_1 = loose_edge_data[pos * 2 + 0];
EditLoopData &value_2 = loose_edge_data[pos * 2 + 1];
if (const BMEdge *bm_edge = bm_original_edge_get(mr, edge_i)) {
value_1 = {};
mesh_render_data_edge_flag(mr, bm_edge, value_1);
value_2 = value_1;
}
else {
value_2 = value_1 = {};
}
const int2 edge = edges[edge_i];
if (const BMVert *bm_vert = bm_original_vert_get(mr, edge[0])) {
mesh_render_data_vert_flag(mr, bm_vert, value_1);
}
if (const BMVert *bm_vert = bm_original_vert_get(mr, edge[1])) {
mesh_render_data_vert_flag(mr, bm_vert, value_2);
}
},
exec_mode::grain_size(2048));
mr.loose_verts.foreach_index(
[&](const int vert, const int pos) {
loose_vert_data[pos] = {};
if (const BMVert *eve = bm_original_vert_get(mr, vert)) {
mesh_render_data_vert_flag(mr, eve, loose_vert_data[pos]);
}
},
exec_mode::grain_size(2048));
}
static void extract_edit_data_bm(const MeshRenderData &mr, MutableSpan<EditLoopData> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
const BMesh &bm = *mr.bm;
const BMUVOffsets uv_offsets_none = BMUVOFFSETS_NONE;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
EditLoopData face_value = {};
mesh_render_data_face_flag(mr, &face, uv_offsets_none, face_value);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
EditLoopData &value = corners_data[index];
value = face_value;
mesh_render_data_edge_flag(mr, loop->e, value);
mesh_render_data_vert_flag(mr, loop->v, value);
loop = loop->next;
}
}
});
mr.loose_edges.foreach_index(
[&](const int edge_i, const int pos) {
EditLoopData &value_1 = loose_edge_data[pos * 2 + 0];
EditLoopData &value_2 = loose_edge_data[pos * 2 + 1];
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), edge_i);
value_1 = {};
mesh_render_data_edge_flag(mr, &edge, value_1);
value_2 = value_1;
mesh_render_data_vert_flag(mr, edge.v1, value_1);
mesh_render_data_vert_flag(mr, edge.v2, value_2);
},
exec_mode::grain_size(2048));
mr.loose_verts.foreach_index(
[&](const int vert_i, const int pos) {
loose_vert_data[pos] = {};
const BMVert &vert = *BM_vert_at_index(&const_cast<BMesh &>(bm), vert_i);
mesh_render_data_vert_flag(mr, &vert, loose_vert_data[pos]);
},
exec_mode::grain_size(2048));
}
gpu::VertBufPtr extract_edit_data(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(get_edit_data_format()));
const int size = mr.corners_num + mr.loose_indices_num;
GPU_vertbuf_data_alloc(*vbo, size);
MutableSpan vbo_data = vbo->data<EditLoopData>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_edit_data_mesh(mr, vbo_data);
}
else {
extract_edit_data_bm(mr, vbo_data);
}
return vbo;
}
static void extract_edit_subdiv_data_mesh(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
MutableSpan<EditLoopData> vbo_data)
{
const BMUVOffsets uv_offsets_none = BMUVOFFSETS_NONE;
const int corners_num = subdiv_cache.num_subdiv_loops;
const int loose_edges_num = mr.loose_edges.size();
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index, corners_num);
const Span<int> subdiv_loop_vert_index = subdiv_cache.verts_orig_index->data<int>();
/* NOTE: #subdiv_loop_edge_index already has the origindex layer baked in. */
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
MutableSpan corners_data = vbo_data.take_front(corners_num);
MutableSpan loose_edge_data = vbo_data.slice(corners_num, loose_edges_num * verts_per_edge);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
threading::parallel_for(IndexRange(subdiv_cache.num_subdiv_quads), 2048, [&](IndexRange range) {
for (const int subdiv_quad : range) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
EditLoopData face_value = {};
if (const BMFace *bm_face = bm_original_face_get(mr, coarse_face)) {
mesh_render_data_face_flag(mr, bm_face, uv_offsets_none, face_value);
}
for (const int subdiv_corner : IndexRange(subdiv_quad * 4, 4)) {
EditLoopData &value = corners_data[subdiv_corner];
value = face_value;
const int vert_origindex = subdiv_loop_vert_index[subdiv_corner];
if (vert_origindex != -1) {
if (const BMVert *bm_vert = bm_original_vert_get(mr, vert_origindex)) {
mesh_render_data_vert_flag(mr, bm_vert, value);
}
}
const int edge_origindex = subdiv_loop_edge_index[subdiv_corner];
if (edge_origindex != -1) {
if (const BMEdge *bm_edge = BM_edge_at_index(mr.bm, edge_origindex)) {
mesh_render_data_edge_flag(mr, bm_edge, value);
}
}
}
}
});
const Span<int2> edges = mr.edges;
mr.loose_edges.foreach_index(
[&](const int edge_i, const int pos) {
MutableSpan<EditLoopData> data = loose_edge_data.slice(pos * verts_per_edge,
verts_per_edge);
if (const BMEdge *edge = bm_original_edge_get(mr, edge_i)) {
EditLoopData value{};
mesh_render_data_edge_flag(mr, edge, value);
data.fill(value);
}
else {
data.fill({});
}
const int2 edge = edges[edge_i];
if (const BMVert *bm_vert = bm_original_vert_get(mr, edge[0])) {
mesh_render_data_vert_flag(mr, bm_vert, data.first());
}
if (const BMVert *bm_vert = bm_original_vert_get(mr, edge[1])) {
mesh_render_data_vert_flag(mr, bm_vert, data.last());
}
},
exec_mode::grain_size(2048));
mr.loose_verts.foreach_index(
[&](const int vert, const int pos) {
loose_vert_data[pos] = {};
if (const BMVert *eve = bm_original_vert_get(mr, vert)) {
mesh_render_data_vert_flag(mr, eve, loose_vert_data[pos]);
}
},
exec_mode::grain_size(2048));
}
static void extract_edit_subdiv_data_bm(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
MutableSpan<EditLoopData> vbo_data)
{
const BMUVOffsets uv_offsets_none = BMUVOFFSETS_NONE;
const int corners_num = subdiv_cache.num_subdiv_loops;
const int loose_edges_num = mr.loose_edges.size();
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index, corners_num);
const Span<int> subdiv_loop_vert_index = subdiv_cache.verts_orig_index->data<int>();
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
MutableSpan corners_data = vbo_data.take_front(corners_num);
MutableSpan loose_edge_data = vbo_data.slice(corners_num, loose_edges_num * verts_per_edge);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(subdiv_cache.num_subdiv_quads), 2048, [&](IndexRange range) {
for (const int subdiv_quad : range) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
const BMFace *bm_face = BM_face_at_index(&bm, coarse_face);
EditLoopData face_value = {};
mesh_render_data_face_flag(mr, bm_face, uv_offsets_none, face_value);
for (const int subdiv_corner : IndexRange(subdiv_quad * 4, 4)) {
EditLoopData &value = corners_data[subdiv_corner];
value = face_value;
const int vert_origindex = subdiv_loop_vert_index[subdiv_corner];
if (vert_origindex != -1) {
const BMVert *bm_vert = BM_vert_at_index(mr.bm, vert_origindex);
mesh_render_data_vert_flag(mr, bm_vert, value);
}
const int edge_origindex = subdiv_loop_edge_index[subdiv_corner];
if (edge_origindex != -1) {
const BMEdge *bm_edge = BM_edge_at_index(mr.bm, edge_origindex);
mesh_render_data_edge_flag(mr, bm_edge, value);
}
}
}
});
mr.loose_edges.foreach_index(
[&](const int edge_i, const int pos) {
MutableSpan<EditLoopData> data = loose_edge_data.slice(pos * verts_per_edge,
verts_per_edge);
const BMEdge *edge = BM_edge_at_index(&bm, edge_i);
EditLoopData value{};
mesh_render_data_edge_flag(mr, edge, value);
data.fill(value);
mesh_render_data_vert_flag(mr, edge->v1, data.first());
mesh_render_data_vert_flag(mr, edge->v2, data.last());
},
exec_mode::grain_size(2048));
mr.loose_verts.foreach_index(
[&](const int vert_i, const int pos) {
loose_vert_data[pos] = {};
const BMVert *vert = BM_vert_at_index(&bm, vert_i);
mesh_render_data_vert_flag(mr, vert, loose_vert_data[pos]);
},
exec_mode::grain_size(2048));
}
gpu::VertBufPtr extract_edit_data_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(get_edit_data_format()));
const int size = subdiv_full_vbo_size(mr, subdiv_cache);
GPU_vertbuf_data_alloc(*vbo, size);
MutableSpan vbo_data = vbo->data<EditLoopData>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_edit_subdiv_data_mesh(mr, subdiv_cache, vbo_data);
}
else {
extract_edit_subdiv_data_bm(mr, subdiv_cache, vbo_data);
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,229 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_mesh.hh"
#include "extract_mesh.hh"
#include "draw_cache_impl.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
/* ---------------------------------------------------------------------- */
/** \name Extract Edit UV Data / Flags
* \{ */
static const GPUVertFormat &edituv_data_format()
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
/* WARNING: Adjust #EditLoopData struct accordingly. */
GPU_vertformat_attr_add(&format, "data", gpu::VertAttrType::UINT_8_8_8_8);
GPU_vertformat_alias_add(&format, "flag");
return format;
}();
return format;
}
static void extract_edituv_data_bm(const MeshRenderData &mr, MutableSpan<EditLoopData> vbo_data)
{
const BMesh &bm = *mr.bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(&bm);
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
EditLoopData face_value = {};
mesh_render_data_face_flag(mr, &face, offsets, face_value);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
EditLoopData &value = vbo_data[index];
value = face_value;
mesh_render_data_loop_flag(mr, loop, offsets, value);
mesh_render_data_loop_edge_flag(mr, loop, offsets, value);
loop = loop->next;
}
}
});
}
static void extract_edituv_data_mesh(const MeshRenderData &mr, MutableSpan<EditLoopData> vbo_data)
{
const BMesh &bm = *mr.bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(&bm);
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Span<int> corner_edges = mr.corner_edges;
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
BMFace *face_orig = bm_original_face_get(mr, face_index);
if (!face_orig) {
vbo_data.slice(face).fill({});
continue;
}
for (const int corner : face) {
EditLoopData &value = vbo_data[corner];
value = {};
BMVert *vert = bm_original_vert_get(mr, corner_verts[corner]);
BMEdge *edge = bm_original_edge_get(mr, corner_edges[corner]);
if (edge && vert) {
/* Loop on an edge endpoint. */
BMLoop *l = BM_face_edge_share_loop(face_orig, edge);
mesh_render_data_loop_flag(mr, l, offsets, value);
mesh_render_data_loop_edge_flag(mr, l, offsets, value);
}
else {
if (edge == nullptr) {
/* Find if the loop's vert is not part of an edit edge.
* For this, we check if the previous loop was on an edge. */
const int corner_prev = bke::mesh::face_corner_prev(face, corner);
edge = bm_original_edge_get(mr, corner_edges[corner_prev]);
}
if (edge) {
/* Mapped points on an edge between two edit verts. */
BMLoop *l = BM_face_edge_share_loop(face_orig, edge);
mesh_render_data_loop_edge_flag(mr, l, offsets, value);
}
}
}
}
});
}
gpu::VertBufPtr extract_edituv_data(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(edituv_data_format()));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
MutableSpan vbo_data = vbo->data<EditLoopData>();
if (mr.extract_type == MeshExtractType::BMesh) {
extract_edituv_data_bm(mr, vbo_data);
}
else {
extract_edituv_data_mesh(mr, vbo_data);
}
return vbo;
}
static void extract_edituv_data_iter_subdiv_bm(const MeshRenderData &mr,
const BMUVOffsets &offsets,
const Span<int> subdiv_loop_vert_index,
const Span<int> subdiv_loop_edge_index,
const int subdiv_quad_index,
const BMFace *coarse_quad,
MutableSpan<EditLoopData> vbo_data)
{
uint start_loop_idx = subdiv_quad_index * 4;
uint end_loop_idx = (subdiv_quad_index + 1) * 4;
EditLoopData edit_loop_data_face = {};
mesh_render_data_face_flag(mr, coarse_quad, offsets, edit_loop_data_face);
for (uint i = start_loop_idx; i < end_loop_idx; i++) {
const int vert_origindex = subdiv_loop_vert_index[i];
int edge_origindex = subdiv_loop_edge_index[i];
EditLoopData *edit_loop_data = &vbo_data[i];
*edit_loop_data = edit_loop_data_face;
if (vert_origindex != -1 && edge_origindex != -1) {
BMEdge *eed = BM_edge_at_index(mr.bm, edge_origindex);
/* Loop on an edge endpoint. */
BMLoop *l = BM_face_edge_share_loop(const_cast<BMFace *>(coarse_quad), eed);
mesh_render_data_loop_flag(mr, l, offsets, *edit_loop_data);
mesh_render_data_loop_edge_flag(mr, l, offsets, *edit_loop_data);
}
else {
if (edge_origindex == -1) {
/* Find if the loop's vert is not part of an edit edge.
* For this, we check if the previous loop was on an edge. */
const uint loop_index_last = (i == start_loop_idx) ? end_loop_idx - 1 : i - 1;
edge_origindex = subdiv_loop_edge_index[loop_index_last];
}
if (edge_origindex != -1) {
/* Mapped points on an edge between two edit verts. */
BMEdge *eed = BM_edge_at_index(mr.bm, edge_origindex);
BMLoop *l = BM_face_edge_share_loop(const_cast<BMFace *>(coarse_quad), eed);
mesh_render_data_loop_edge_flag(mr, l, offsets, *edit_loop_data);
}
}
}
}
static void extract_edituv_subdiv_data_bm(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
MutableSpan<EditLoopData> vbo_data)
{
const int corners_num = subdiv_cache.num_subdiv_loops;
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index, corners_num);
const Span<int> subdiv_loop_vert_index = subdiv_cache.verts_orig_index->data<int>();
/* NOTE: #subdiv_loop_edge_index already has the origindex layer baked in. */
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
const BMUVOffsets offsets = BM_uv_map_offsets_get(mr.bm);
threading::parallel_for(IndexRange(subdiv_cache.num_subdiv_quads), 2048, [&](IndexRange range) {
for (const int subdiv_quad : range) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
extract_edituv_data_iter_subdiv_bm(mr,
offsets,
subdiv_loop_vert_index,
subdiv_loop_edge_index,
subdiv_quad,
BM_face_at_index(mr.bm, coarse_face),
vbo_data);
}
});
}
static void extract_edituv_subdiv_data_mesh(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
MutableSpan<EditLoopData> vbo_data)
{
const int corners_num = subdiv_cache.num_subdiv_loops;
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index, corners_num);
const Span<int> subdiv_loop_vert_index = subdiv_cache.verts_orig_index->data<int>();
/* NOTE: #subdiv_loop_edge_index already has the origindex layer baked in. */
const Span<int> subdiv_loop_edge_index = subdiv_cache.edges_orig_index->data<int>();
const BMUVOffsets offsets = BM_uv_map_offsets_get(mr.bm);
threading::parallel_for(IndexRange(subdiv_cache.num_subdiv_quads), 2048, [&](IndexRange range) {
for (const int subdiv_quad : range) {
const int coarse_face = subdiv_loop_face_index[subdiv_quad * 4];
extract_edituv_data_iter_subdiv_bm(mr,
offsets,
subdiv_loop_vert_index,
subdiv_loop_edge_index,
subdiv_quad,
bm_original_face_get(mr, coarse_face),
vbo_data);
}
});
}
gpu::VertBufPtr extract_edituv_data_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(edituv_data_format()));
const int size = subdiv_cache.num_subdiv_loops;
GPU_vertbuf_data_alloc(*vbo, size);
MutableSpan vbo_data = vbo->data<EditLoopData>();
if (mr.extract_type == MeshExtractType::BMesh) {
extract_edituv_subdiv_data_bm(mr, subdiv_cache, vbo_data);
}
else {
extract_edituv_subdiv_data_mesh(mr, subdiv_cache, vbo_data);
}
return vbo;
}
/** \} */
} // namespace blender::draw

View File

@@ -0,0 +1,268 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_math_vector.h"
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
struct UVStretchAngle {
float angle;
int16_t uv_angles[2];
};
BLI_STATIC_ASSERT_ALIGN(UVStretchAngle, 4)
struct MeshExtract_StretchAngle_Data {
UVStretchAngle *vbo_data;
const float2 *uv;
float auv[2][2], last_auv[2];
float av[2][3], last_av[3];
int cd_ofs;
};
static void compute_normalize_edge_vectors(float auv[2][2],
float av[2][3],
const float uv[2],
const float uv_prev[2],
const float co[3],
const float co_prev[3])
{
/* Move previous edge. */
copy_v2_v2(auv[0], auv[1]);
copy_v3_v3(av[0], av[1]);
/* 2d edge */
sub_v2_v2v2(auv[1], uv_prev, uv);
normalize_v2(auv[1]);
/* 3d edge */
sub_v3_v3v3(av[1], co_prev, co);
normalize_v3(av[1]);
}
static short v2_to_short_angle(const float v[2])
{
return atan2f(v[1], v[0]) * float(M_1_PI) * SHRT_MAX;
}
static void edituv_get_edituv_stretch_angle(float auv[2][2],
const float av[2][3],
UVStretchAngle *r_stretch)
{
/* Send UVs to the shader and let it compute the aspect corrected angle. */
r_stretch->uv_angles[0] = v2_to_short_angle(auv[0]);
r_stretch->uv_angles[1] = v2_to_short_angle(auv[1]);
/* Compute 3D angle here. */
r_stretch->angle = angle_normalized_v3v3(av[0], av[1]) * float(M_1_PI);
#if 0 /* here for reference, this is done in shader now. */
float uvang = angle_normalized_v2v2(auv0, auv1);
float ang = angle_normalized_v3v3(av0, av1);
float stretch = fabsf(uvang - ang) / float(M_PI);
return 1.0f - pow2f(1.0f - stretch);
#endif
}
static void extract_uv_stretch_angle_bm(const MeshRenderData &mr,
MutableSpan<UVStretchAngle> vbo_data)
{
const BMesh &bm = *mr.bm;
const StringRef active_name = mr.mesh->active_uv_map_name();
const int uv_offset = CustomData_get_offset_named(&bm.ldata, CD_PROP_FLOAT2, active_name);
float auv[2][2], last_auv[2];
float av[2][3], last_av[3];
const BMFace *face;
BMIter f_iter;
BM_ITER_MESH (face, &f_iter, &const_cast<BMesh &>(bm), BM_FACES_OF_MESH) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(face);
do {
const int l_index = BM_elem_index_get(l_iter);
const float (*luv)[2], (*luv_next)[2];
BMLoop *l_next = l_iter->next;
if (l_iter == BM_FACE_FIRST_LOOP(face)) {
/* First loop in face. */
BMLoop *l_tmp = l_iter->prev;
BMLoop *l_next_tmp = l_iter;
luv = BM_ELEM_CD_GET_FLOAT2_P(l_tmp, uv_offset);
luv_next = BM_ELEM_CD_GET_FLOAT2_P(l_next_tmp, uv_offset);
compute_normalize_edge_vectors(auv,
av,
*luv,
*luv_next,
bm_vert_co_get(mr, l_tmp->v),
bm_vert_co_get(mr, l_next_tmp->v));
/* Save last edge. */
copy_v2_v2(last_auv, auv[1]);
copy_v3_v3(last_av, av[1]);
}
if (l_next == BM_FACE_FIRST_LOOP(face)) {
/* Move previous edge. */
copy_v2_v2(auv[0], auv[1]);
copy_v3_v3(av[0], av[1]);
/* Copy already calculated last edge. */
copy_v2_v2(auv[1], last_auv);
copy_v3_v3(av[1], last_av);
}
else {
luv = BM_ELEM_CD_GET_FLOAT2_P(l_iter, uv_offset);
luv_next = BM_ELEM_CD_GET_FLOAT2_P(l_next, uv_offset);
compute_normalize_edge_vectors(auv,
av,
*luv,
*luv_next,
bm_vert_co_get(mr, l_iter->v),
bm_vert_co_get(mr, l_next->v));
}
edituv_get_edituv_stretch_angle(auv, av, &vbo_data[l_index]);
} while ((l_iter = l_iter->next) != l_first);
}
}
static void extract_uv_stretch_angle_mesh(const MeshRenderData &mr,
MutableSpan<UVStretchAngle> vbo_data)
{
const Span<float3> positions = mr.vert_positions;
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Mesh &mesh = *mr.mesh;
const bke::AttributeAccessor attributes = mesh.attributes();
const StringRef name = mesh.active_uv_map_name();
const VArraySpan uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Corner);
float auv[2][2], last_auv[2];
float av[2][3], last_av[3];
for (const int face_index : faces.index_range()) {
const IndexRange face = faces[face_index];
const int corner_end = face.start() + face.size();
for (int corner = face.start(); corner < corner_end; corner += 1) {
int l_next = corner + 1;
if (corner == face.start()) {
/* First loop in face. */
const int corner_last = corner_end - 1;
const int l_next_tmp = face.start();
compute_normalize_edge_vectors(auv,
av,
uv_map[corner_last],
uv_map[l_next_tmp],
positions[corner_verts[corner_last]],
positions[corner_verts[l_next_tmp]]);
/* Save last edge. */
copy_v2_v2(last_auv, auv[1]);
copy_v3_v3(last_av, av[1]);
}
if (l_next == corner_end) {
l_next = face.start();
/* Move previous edge. */
copy_v2_v2(auv[0], auv[1]);
copy_v3_v3(av[0], av[1]);
/* Copy already calculated last edge. */
copy_v2_v2(auv[1], last_auv);
copy_v3_v3(av[1], last_av);
}
else {
compute_normalize_edge_vectors(auv,
av,
uv_map[corner],
uv_map[l_next],
positions[corner_verts[corner]],
positions[corner_verts[l_next]]);
}
edituv_get_edituv_stretch_angle(auv, av, &vbo_data[corner]);
}
}
}
gpu::VertBufPtr extract_edituv_stretch_angle(const MeshRenderData &mr)
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
/* Waning: adjust #UVStretchAngle struct accordingly. */
GPU_vertformat_attr_add(&format, "angle", gpu::VertAttrType::SFLOAT_32);
GPU_vertformat_attr_add(&format, "uv_angles", gpu::VertAttrType::SNORM_16_16);
return format;
}();
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
MutableSpan vbo_data = vbo->data<UVStretchAngle>();
if (mr.extract_type == MeshExtractType::BMesh) {
extract_uv_stretch_angle_bm(mr, vbo_data);
}
else {
extract_uv_stretch_angle_mesh(mr, vbo_data);
}
return vbo;
}
static const GPUVertFormat &get_edituv_stretch_angle_format_subdiv()
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
/* Waning: adjust #UVStretchAngle struct accordingly. */
GPU_vertformat_attr_add(&format, "angle", gpu::VertAttrType::SFLOAT_32);
GPU_vertformat_attr_add(&format, "uv_angles", gpu::VertAttrType::SFLOAT_32_32);
return format;
}();
return format;
}
gpu::VertBufPtr extract_edituv_stretch_angle_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_on_device(
get_edituv_stretch_angle_format_subdiv(), subdiv_cache.num_subdiv_loops));
gpu::VertBuf *pos = cache.final.buff.vbos.lookup(VBOType::Position).get();
gpu::VertBuf *uvs = cache.final.buff.vbos.lookup(VBOType::UVs).get();
/* It may happen that the data for the UV editor is requested before (as a separate draw update)
* the data for the mesh when switching to the `UV Editing` workspace, and therefore the position
* buffer might not be created yet. In this case, create a buffer it locally, the subdivision
* data should already be evaluated if we are here. This can happen if the subsurf modifier is
* only enabled in edit-mode. See #96338. */
if (!pos) {
pos = GPU_vertbuf_calloc();
static const GPUVertFormat pos_format = GPU_vertformat_from_attribute(
"pos", gpu::VertAttrType::SFLOAT_32_32_32);
GPU_vertbuf_init_build_on_device(*pos, pos_format, subdiv_full_vbo_size(mr, subdiv_cache));
draw_subdiv_extract_pos(subdiv_cache, pos, nullptr);
}
/* UVs are stored contiguously so we need to compute the offset in the UVs buffer for the active
* UV layer. */
VectorSet<std::string> uv_layers = cache.cd_used.uv;
/* HACK to fix #68857 */
if (mr.extract_type == MeshExtractType::BMesh && cache.cd_used.edit_uv == 1) {
const StringRef active_name = mr.mesh->active_uv_map_name();
if (!active_name.is_empty()) {
uv_layers.add_as(active_name);
}
}
int uvs_offset = uv_layers.index_of(mr.mesh->active_uv_map_name());
/* The data is at `offset * num loops`, and we have 2 values per index. */
uvs_offset *= subdiv_cache.num_subdiv_loops * 2;
draw_subdiv_build_edituv_stretch_angle_buffer(subdiv_cache, pos, uvs, uvs_offset, vbo.get());
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,158 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_math_geom.h"
#include "BLI_math_vector_types.hh"
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
BLI_INLINE float area_ratio_get(float area, float uvarea)
{
if (area >= FLT_EPSILON && uvarea >= FLT_EPSILON) {
return uvarea / area;
}
return 0.0f;
}
BLI_INLINE float area_ratio_to_stretch(float ratio, float tot_ratio)
{
ratio *= tot_ratio;
return (ratio > 1.0f) ? (1.0f / ratio) : ratio;
}
struct AreaInfo {
float tot_area = 0.0f;
float tot_uv_area = 0.0f;
};
static AreaInfo compute_area_ratio(const MeshRenderData &mr, MutableSpan<float> r_area_ratio)
{
if (mr.extract_type == MeshExtractType::BMesh) {
const BMesh &bm = *mr.bm;
const StringRef active_name = mr.mesh->active_uv_map_name();
const int uv_offset = CustomData_get_offset_named(&bm.ldata, CD_PROP_FLOAT2, active_name);
return threading::parallel_reduce(
IndexRange(bm.totface),
1024,
AreaInfo{},
[&](const IndexRange range, AreaInfo info) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const float area = BM_face_calc_area(&face);
const float uvarea = BM_face_calc_area_uv(&face, uv_offset);
info.tot_area += area;
info.tot_uv_area += uvarea;
r_area_ratio[face_index] = area_ratio_get(area, uvarea);
}
return info;
},
[](const AreaInfo &a, const AreaInfo &b) {
return AreaInfo{a.tot_area + b.tot_area, a.tot_uv_area + b.tot_uv_area};
});
}
const Span<float3> positions = mr.vert_positions;
const OffsetIndices<int> faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Mesh &mesh = *mr.mesh;
const bke::AttributeAccessor attributes = mesh.attributes();
const StringRef name = mesh.active_uv_map_name();
const VArraySpan uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Corner);
return threading::parallel_reduce(
faces.index_range(),
1024,
AreaInfo{},
[&](const IndexRange range, AreaInfo info) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
const float area = bke::mesh::face_area_calc(positions, corner_verts.slice(face));
float uvarea = area_poly_v2(reinterpret_cast<const float (*)[2]>(&uv_map[face.start()]),
face.size());
info.tot_area += area;
info.tot_uv_area += uvarea;
r_area_ratio[face_index] = area_ratio_get(area, uvarea);
}
return info;
},
[](const AreaInfo &a, const AreaInfo &b) {
return AreaInfo{a.tot_area + b.tot_area, a.tot_uv_area + b.tot_uv_area};
});
}
gpu::VertBufPtr extract_edituv_stretch_area(const MeshRenderData &mr,
float &tot_area,
float &tot_uv_area)
{
Array<float> area_ratio(mr.faces_num);
const AreaInfo info = compute_area_ratio(mr, area_ratio);
tot_area = info.tot_area;
tot_uv_area = info.tot_uv_area;
static const GPUVertFormat format = GPU_vertformat_from_attribute("ratio",
gpu::VertAttrType::SFLOAT_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
MutableSpan<float> vbo_data = vbo->data<float>();
const int64_t bytes = area_ratio.as_span().size_in_bytes() + vbo_data.size_in_bytes();
threading::memory_bandwidth_bound_task(bytes, [&]() {
if (mr.extract_type == MeshExtractType::BMesh) {
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
vbo_data.slice(face_range).fill(area_ratio[face_index]);
}
});
}
else {
BLI_assert(mr.extract_type == MeshExtractType::Mesh);
const OffsetIndices<int> faces = mr.faces;
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face : range) {
vbo_data.slice(faces[face]).fill(area_ratio[face]);
}
});
}
});
return vbo;
}
gpu::VertBufPtr extract_edituv_stretch_area_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
float &tot_area,
float &tot_uv_area)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute("ratio",
gpu::VertAttrType::SFLOAT_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(format, subdiv_cache.num_subdiv_loops));
gpu::VertBuf *coarse_vbo = GPU_vertbuf_calloc();
GPU_vertbuf_init_with_format(*coarse_vbo, format);
GPU_vertbuf_data_alloc(*coarse_vbo, mr.faces_num);
MutableSpan coarse_vbo_data = coarse_vbo->data<float>();
const AreaInfo info = compute_area_ratio(mr, coarse_vbo_data);
tot_area = info.tot_area;
tot_uv_area = info.tot_uv_area;
draw_subdiv_build_edituv_stretch_area_buffer(subdiv_cache, coarse_vbo, vbo.get());
GPU_vertbuf_discard(coarse_vbo);
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "extract_mesh.hh"
#include "draw_cache_impl.hh"
namespace blender::draw {
gpu::VertBufPtr extract_face_dots_edituv_data(const MeshRenderData &mr)
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "data", gpu::VertAttrType::UINT_8_8_8_8);
GPU_vertformat_alias_add(&format, "flag");
return format;
}();
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.faces_num);
MutableSpan vbo_data = vbo->data<EditLoopData>();
const BMesh &bm = *mr.bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(&bm);
if (mr.extract_type == MeshExtractType::BMesh) {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
vbo_data[face_index] = {};
mesh_render_data_face_flag(mr, &face, offsets, vbo_data[face_index]);
}
});
}
else {
if (mr.orig_index_face) {
const Span<int> orig_index_face(mr.orig_index_face, mr.faces_num);
threading::parallel_for(IndexRange(mr.faces_num), 4096, [&](const IndexRange range) {
for (const int face : range) {
vbo_data[face] = {};
if (orig_index_face[face] == ORIGINDEX_NONE) {
continue;
}
const BMFace *orig_face = bm_original_face_get(mr, face);
mesh_render_data_face_flag(mr, orig_face, offsets, vbo_data[face]);
}
});
}
else {
vbo_data.fill({});
}
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "extract_mesh.hh"
#include "GPU_attribute_convert.hh"
namespace blender::draw {
#define NOR_AND_FLAG_DEFAULT 0
#define NOR_AND_FLAG_SELECT 1
#define NOR_AND_FLAG_ACTIVE -1
#define NOR_AND_FLAG_HIDDEN -2
template<typename GPUType>
static void extract_face_dot_normals_mesh(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
gpu::convert_normals(mr.face_normals, normals);
const GPUType invalid_normal = gpu::convert_normal<GPUType>(float3(0));
threading::parallel_for(IndexRange(mr.faces_num), 4096, [&](const IndexRange range) {
for (const int i : range) {
const BMFace *face = bm_original_face_get(mr, i);
if (!face || BM_elem_flag_test(face, BM_ELEM_HIDDEN)) {
normals[i] = invalid_normal;
normals[i].w = NOR_AND_FLAG_HIDDEN;
}
else if (BM_elem_flag_test(face, BM_ELEM_SELECT)) {
normals[i].w = (face == mr.efa_act) ? NOR_AND_FLAG_ACTIVE : NOR_AND_FLAG_SELECT;
}
}
});
}
template<typename GPUType>
void extract_face_dot_normals_bm(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const GPUType invalid_normal = gpu::convert_normal<GPUType>(float3(0));
threading::parallel_for(IndexRange(mr.faces_num), 4096, [&](const IndexRange range) {
for (const int i : range) {
BMFace *face = BM_face_at_index(mr.bm, i);
if (BM_elem_flag_test(face, BM_ELEM_HIDDEN)) {
normals[i] = invalid_normal;
normals[i].w = NOR_AND_FLAG_HIDDEN;
}
else {
normals[i] = gpu::convert_normal<GPUType>(bm_face_no_get(mr, face));
normals[i].w = (BM_elem_flag_test(face, BM_ELEM_SELECT) ?
((face == mr.efa_act) ? NOR_AND_FLAG_ACTIVE : NOR_AND_FLAG_SELECT) :
NOR_AND_FLAG_DEFAULT);
}
}
});
}
gpu::VertBufPtr extract_face_dot_normals(const MeshRenderData &mr, const bool use_hq)
{
if (use_hq) {
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"norAndFlag", gpu::VertAttrType::SNORM_16_16_16_16);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.faces_num);
MutableSpan vbo_data = vbo->data<short4>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_face_dot_normals_mesh(mr, vbo_data);
}
else {
extract_face_dot_normals_bm(mr, vbo_data);
}
return vbo;
}
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"norAndFlag", gpu::VertAttrType::SNORM_10_10_10_2);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.faces_num);
MutableSpan vbo_data = vbo->data<gpu::PackedNormal>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_face_dot_normals_mesh(mr, vbo_data);
}
else {
extract_face_dot_normals_bm(mr, vbo_data);
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "GPU_index_buffer.hh"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static const GPUVertFormat &get_fdots_pos_format()
{
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"pos", gpu::VertAttrType::SFLOAT_32_32_32);
return format;
}
static const GPUVertFormat &get_fdots_nor_format_subdiv()
{
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"norAndFlag", gpu::VertAttrType::SFLOAT_32_32_32_32);
return format;
}
static void extract_face_dot_positions_mesh(const MeshRenderData &mr, MutableSpan<float3> vbo_data)
{
const Span<float3> positions = mr.vert_positions;
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
if (mr.use_subsurf_fdots) {
const BitSpan facedot_tags = mr.mesh->runtime->subsurf_face_dot_tags;
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
for (const int face : range) {
const Span<int> face_verts = corner_verts.slice(faces[face]);
const int *vert = std::find_if(face_verts.begin(), face_verts.end(), [&](const int vert) {
return facedot_tags[vert].test();
});
if (vert == face_verts.end()) {
vbo_data[face] = float3(0);
}
else {
vbo_data[face] = positions[*vert];
}
}
});
}
else {
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
for (const int face : range) {
vbo_data[face] = bke::mesh::face_center_calc(positions, corner_verts.slice(faces[face]));
}
});
}
}
static void extract_face_dot_positions_bm(const MeshRenderData &mr, MutableSpan<float3> vbo_data)
{
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
if (mr.bm_vert_coords.is_empty()) {
BM_face_calc_center_median(&face, vbo_data[face_index]);
}
else {
BM_face_calc_center_median_vcos(&bm, &face, vbo_data[face_index], mr.bm_vert_coords);
}
}
});
}
gpu::VertBufPtr extract_face_dots_position(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(get_fdots_pos_format()));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num + mr.loose_indices_num);
MutableSpan vbo_data = vbo->data<float3>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_face_dot_positions_mesh(mr, vbo_data);
}
else {
extract_face_dot_positions_bm(mr, vbo_data);
}
return vbo;
}
void extract_face_dots_subdiv(const DRWSubdivCache &subdiv_cache,
gpu::VertBufPtr &fdots_pos,
gpu::VertBufPtr *fdots_nor,
gpu::IndexBufPtr &fdots)
{
/* We "extract" positions, normals, and indices at once. */
/* The normals may not be requested. */
if (fdots_nor) {
*fdots_nor = gpu::VertBufPtr(GPU_vertbuf_create_on_device(get_fdots_nor_format_subdiv(),
subdiv_cache.num_coarse_faces));
}
fdots_pos = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(get_fdots_pos_format(), subdiv_cache.num_coarse_faces));
fdots = gpu::IndexBufPtr(GPU_indexbuf_build_on_device(subdiv_cache.num_coarse_faces));
draw_subdiv_build_fdots_buffers(
subdiv_cache, fdots_pos.get(), fdots_nor ? fdots_nor->get() : nullptr, fdots.get());
}
} // namespace blender::draw

View File

@@ -0,0 +1,88 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BKE_attribute.hh"
#include "extract_mesh.hh"
namespace blender::draw {
static void extract_face_dots_uv_mesh(const MeshRenderData &mr, MutableSpan<float2> vbo_data)
{
const Mesh &mesh = *mr.mesh;
const StringRef name = mesh.active_uv_map_name();
const bke::AttributeAccessor attributes = mesh.attributes();
if (mr.use_subsurf_fdots) {
const BitSpan facedot_tags = mesh.runtime->subsurf_face_dot_tags;
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const VArraySpan uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Corner);
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
const auto corner = std::find_if(face.begin(), face.end(), [&](const int corner) {
return facedot_tags[corner_verts[corner]].test();
});
if (corner == face.end()) {
vbo_data[face_index] = float2(0);
}
else {
vbo_data[face_index] = uv_map[*corner];
}
}
});
}
else {
/* Use the attribute API to average the attribute on the face domain. */
const VArray uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Face);
uv_map.materialize(vbo_data);
}
}
static void extract_face_dots_uv_bm(const MeshRenderData &mr, MutableSpan<float2> vbo_data)
{
const BMesh &bm = *mr.bm;
const int offset = CustomData_get_offset(&bm.ldata, CD_PROP_FLOAT2);
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
vbo_data[face_index] = float2(0);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
vbo_data[face_index] += *BM_ELEM_CD_GET_FLOAT2_P(loop, offset);
loop = loop->next;
}
vbo_data[face_index] /= face.len;
}
});
}
gpu::VertBufPtr extract_face_dots_uv(const MeshRenderData &mr)
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "u", gpu::VertAttrType::SFLOAT_32_32);
GPU_vertformat_alias_add(&format, "au");
GPU_vertformat_alias_add(&format, "pos");
return format;
}();
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.faces_num);
MutableSpan<float2> vbo_data = vbo->data<float2>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_face_dots_uv_mesh(mr, vbo_data);
}
else {
extract_face_dots_uv_bm(mr, vbo_data);
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,364 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "GPU_attribute_convert.hh"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
template<typename GPUType>
static void extract_vert_normals(const Span<int> corner_verts,
const Span<float3> vert_normals,
MutableSpan<GPUType> normals)
{
Array<GPUType> vert_normals_converted(vert_normals.size());
gpu::convert_normals(vert_normals, vert_normals_converted.as_mutable_span());
array_utils::gather(vert_normals_converted.as_span(), corner_verts, normals);
}
template<typename GPUType>
static void extract_face_normals(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const OffsetIndices faces = mr.faces;
const Span<float3> face_normals = mr.face_normals;
threading::parallel_for(faces.index_range(), 4096, [&](const IndexRange range) {
for (const int face : range) {
normals.slice(faces[face]).fill(gpu::convert_normal<GPUType>(face_normals[face]));
}
});
}
template<typename GPUType>
static void extract_normals_mesh(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const auto get_vert_normals = [&]() {
return mr.use_simplify_normals ? mr.mesh->vert_normals_true() : mr.mesh->vert_normals();
};
if (mr.normals_domain == bke::MeshNormalDomain::Face) {
extract_face_normals(mr, normals);
}
else if (mr.normals_domain == bke::MeshNormalDomain::Point) {
extract_vert_normals(mr.corner_verts, get_vert_normals(), normals);
}
else if (!mr.corner_normals.is_empty()) {
gpu::convert_normals(mr.corner_normals, normals);
}
else if (mr.sharp_faces.is_empty()) {
extract_vert_normals(mr.corner_verts, get_vert_normals(), normals);
}
else {
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const Span<bool> sharp_faces = mr.sharp_faces;
const Span<float3> vert_normals = get_vert_normals();
const Span<float3> face_normals = mr.face_normals;
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face : range) {
if (sharp_faces[face]) {
normals.slice(faces[face]).fill(gpu::convert_normal<GPUType>(face_normals[face]));
}
else {
for (const int corner : faces[face]) {
normals[corner] = gpu::convert_normal<GPUType>(vert_normals[corner_verts[corner]]);
}
}
}
});
}
}
template<typename GPUType>
static void extract_vert_normals_bm(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const BMesh &bm = *mr.bm;
if (mr.bm_free_normal_offset_vert != -1) {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
const IndexRange face_range(BM_elem_index_get(loop), face.len);
for (const int corner : face_range) {
normals[corner] = gpu::convert_normal<GPUType>(
BM_ELEM_CD_GET_FLOAT_P(loop->v, mr.bm_free_normal_offset_vert));
loop = loop->next;
}
}
});
}
else if (!mr.bm_vert_normals.is_empty()) {
Array<GPUType> vert_normals_converted(mr.bm_vert_normals.size());
gpu::convert_normals(mr.bm_vert_normals, vert_normals_converted.as_mutable_span());
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
const IndexRange face_range(BM_elem_index_get(loop), face.len);
for (const int corner : face_range) {
normals[corner] = vert_normals_converted[BM_elem_index_get(loop->v)];
loop = loop->next;
}
}
});
}
else {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
const IndexRange face_range(BM_elem_index_get(loop), face.len);
for (const int corner : face_range) {
normals[corner] = gpu::convert_normal<GPUType>(loop->v->no);
loop = loop->next;
}
}
});
}
}
template<typename GPUType>
static void extract_face_normals_bm(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const BMesh &bm = *mr.bm;
if (mr.bm_free_normal_offset_face != -1) {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
normals.slice(face_range)
.fill(gpu::convert_normal<GPUType>(
BM_ELEM_CD_GET_FLOAT_P(&face, mr.bm_free_normal_offset_face)));
}
});
}
else if (!mr.bm_face_normals.is_empty()) {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
normals.slice(face_range)
.fill(gpu::convert_normal<GPUType>(mr.bm_face_normals[face_index]));
}
});
}
else {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
normals.slice(face_range).fill(gpu::convert_normal<GPUType>(face.no));
}
});
}
}
template<typename GPUType>
static void extract_normals_bm(const MeshRenderData &mr, MutableSpan<GPUType> normals)
{
const BMesh &bm = *mr.bm;
if (mr.normals_domain == bke::MeshNormalDomain::Face) {
extract_face_normals_bm(mr, normals);
}
else if (mr.normals_domain == bke::MeshNormalDomain::Point) {
extract_vert_normals_bm(mr, normals);
}
else if (mr.bm_free_normal_offset_corner != -1) {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
const IndexRange face_range(BM_elem_index_get(loop), face.len);
for (const int corner : face_range) {
normals[corner] = gpu::convert_normal<GPUType>(
BM_ELEM_CD_GET_FLOAT_P(loop, mr.bm_free_normal_offset_corner));
loop = loop->next;
}
}
});
}
else if (!mr.bm_loop_normals.is_empty()) {
gpu::convert_normals(mr.bm_loop_normals, normals);
}
else {
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
const IndexRange face_range(BM_elem_index_get(loop), face.len);
if (!BM_elem_flag_test(&face, BM_ELEM_SMOOTH)) {
if (!mr.bm_face_normals.is_empty()) {
normals.slice(face_range)
.fill(gpu::convert_normal<GPUType>(mr.bm_face_normals[face_index]));
}
else {
normals.slice(face_range).fill(gpu::convert_normal<GPUType>(face.no));
}
}
else {
if (!mr.bm_vert_normals.is_empty()) {
for (const int corner : face_range) {
normals[corner] = gpu::convert_normal<GPUType>(
mr.bm_vert_normals[BM_elem_index_get(loop->v)]);
loop = loop->next;
}
}
else {
for (const int corner : face_range) {
normals[corner] = gpu::convert_normal<GPUType>(loop->v->no);
loop = loop->next;
}
}
}
}
});
}
}
gpu::VertBufPtr extract_normals(const MeshRenderData &mr, const bool use_hq)
{
const int size = mr.corners_num + mr.loose_indices_num;
if (use_hq) {
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SNORM_16_16_16_16);
GPU_vertformat_alias_add(&format, "lnor");
GPU_vertformat_alias_add(&format, "vnor");
return format;
}();
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, size);
MutableSpan vbo_data = vbo->data<short4>();
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_data = vbo_data.take_back(mr.loose_indices_num);
if (mr.extract_type == MeshExtractType::Mesh) {
extract_normals_mesh(mr, corners_data);
}
else {
extract_normals_bm(mr, corners_data);
}
loose_data.fill(short4(0));
return vbo;
}
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SNORM_10_10_10_2);
GPU_vertformat_alias_add(&format, "lnor");
GPU_vertformat_alias_add(&format, "vnor");
return format;
}();
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, size);
MutableSpan vbo_data = vbo->data<gpu::PackedNormal>();
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_data = vbo_data.take_back(mr.loose_indices_num);
if (mr.extract_type == MeshExtractType::Mesh) {
extract_normals_mesh(mr, corners_data);
}
else {
extract_normals_bm(mr, corners_data);
}
loose_data.fill(gpu::PackedNormal{});
return vbo;
}
static const GPUVertFormat &get_normals_format()
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "nor", gpu::VertAttrType::SFLOAT_32_32_32);
GPU_vertformat_alias_add(&format, "lnor");
GPU_vertformat_alias_add(&format, "vnor");
return format;
}();
return format;
}
static void update_loose_normals(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
gpu::VertBuf &lnor)
{
const int vbo_size = subdiv_full_vbo_size(mr, subdiv_cache);
const int loose_geom_start = subdiv_cache.num_subdiv_loops;
/* Push VBO content to the GPU and bind the VBO so that #GPU_vertbuf_update_sub can work. */
GPU_vertbuf_use(&lnor);
/* Default to zeroed attribute. The overlay shader should expect this and render engines should
* never draw loose geometry. */
const float3 default_normal(0.0f, 0.0f, 0.0f);
for (const int i : IndexRange::from_begin_end(loose_geom_start, vbo_size)) {
/* TODO(fclem): This has HORRENDOUS performance. Prefer clearing the buffer on device with
* something like glClearBufferSubData. */
GPU_vertbuf_update_sub(&lnor, i * sizeof(float3), sizeof(float3), &default_normal);
}
}
gpu::VertBufPtr extract_normals_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
gpu::VertBuf &pos)
{
const int vbo_size = subdiv_full_vbo_size(mr, subdiv_cache);
gpu::VertBufPtr lnor = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(get_normals_format(), vbo_size));
if (subdiv_cache.num_subdiv_loops == 0) {
update_loose_normals(mr, subdiv_cache, *lnor);
return lnor;
}
if (subdiv_cache.use_custom_loop_normals) {
const Mesh *coarse_mesh = subdiv_cache.mesh;
static GPUVertFormat src_normals_format = GPU_vertformat_from_attribute(
"vnor", gpu::VertAttrType::SFLOAT_32_32_32);
gpu::VertBufPtr src = gpu::VertBufPtr(GPU_vertbuf_create_with_format(src_normals_format));
GPU_vertbuf_data_alloc(*src, coarse_mesh->corners_num);
src->data<float3>().copy_from(coarse_mesh->corner_normals());
draw_subdiv_interp_corner_normals(subdiv_cache, *src, *lnor);
update_loose_normals(mr, subdiv_cache, *lnor);
return lnor;
}
gpu::VertBufPtr subdiv_corner_verts = gpu::VertBufPtr(draw_subdiv_build_origindex_buffer(
subdiv_cache.subdiv_loop_subdiv_vert_index, subdiv_cache.num_subdiv_loops));
/* Calculate vertex normals (stored here per subdivided vertex rather than per subdivided face
* corner). The values are used for smooth shaded faces later. */
static GPUVertFormat vert_normals_format = GPU_vertformat_from_attribute(
"vnor", gpu::VertAttrType::SFLOAT_32_32_32);
gpu::VertBufPtr vert_normals = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(vert_normals_format, subdiv_cache.num_subdiv_verts));
draw_subdiv_accumulate_normals(subdiv_cache,
&pos,
subdiv_cache.subdiv_vert_face_adjacency_offsets,
subdiv_cache.subdiv_vert_face_adjacency,
subdiv_corner_verts.get(),
vert_normals.get());
/* Compute final normals for face corners, either using the vertex normal corresponding to the
* corner, or by calculating the face normal.
*
* TODO: Avoid using face normals or vertex normals if possible, using `mr.normals_domain`. */
draw_subdiv_build_lnor_buffer(
subdiv_cache, &pos, vert_normals.get(), subdiv_corner_verts.get(), lnor.get());
update_loose_normals(mr, subdiv_cache, *lnor);
return lnor;
}
} // namespace blender::draw

View File

@@ -0,0 +1,602 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_jitter_2d.h"
#include "BLI_map.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_ordered_edge.hh"
#include "BKE_bvhutils.hh"
#include "BKE_editmesh.hh"
#include "BKE_editmesh_bvh.hh"
#include "BKE_editmesh_cache.hh"
#include "extract_mesh.hh"
namespace blender::draw {
static void axis_from_enum_v3(float v[3], const char axis)
{
zero_v3(v);
if (axis < 3) {
v[axis] = 1.0f;
}
else {
v[axis - 3] = -1.0f;
}
}
BLI_INLINE float overhang_remap(float fac, float min, float max, float minmax_irange)
{
if (fac < min) {
fac = 1.0f;
}
else if (fac > max) {
fac = -1.0f;
}
else {
fac = (fac - min) * minmax_irange;
fac = 1.0f - fac;
CLAMP(fac, 0.0f, 1.0f);
}
return fac;
}
static void statvis_calc_overhang(const MeshRenderData &mr,
const float4x4 &object_to_world,
MutableSpan<float> r_overhang)
{
const MeshStatVis *statvis = &mr.toolsettings->statvis;
const float min = statvis->overhang_min / float(M_PI);
const float max = statvis->overhang_max / float(M_PI);
const char axis = statvis->overhang_axis;
float dir[3];
const float minmax_irange = 1.0f / (max - min);
BLI_assert(min <= max);
axis_from_enum_v3(dir, axis);
/* now convert into global space */
mul_transposed_mat3_m4_v3(object_to_world.ptr(), dir);
normalize_v3(dir);
if (mr.extract_type == MeshExtractType::BMesh) {
BMEditMesh *em = mr.edit_bmesh;
BMIter iter;
BMesh *bm = em->bm;
BMFace *f;
int l_index = 0;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
float fac = angle_normalized_v3v3(bm_face_no_get(mr, f), dir) / float(M_PI);
fac = overhang_remap(fac, min, max, minmax_irange);
for (int i = 0; i < f->len; i++, l_index++) {
r_overhang[l_index] = fac;
}
}
}
else {
for (const int face_i : mr.faces.index_range()) {
float fac = angle_normalized_v3v3(mr.face_normals[face_i], dir) / float(M_PI);
fac = overhang_remap(fac, min, max, minmax_irange);
for (const int loop_i : mr.faces[face_i]) {
r_overhang[loop_i] = fac;
}
}
}
}
/**
* Needed so we can use jitter values for face interpolation.
*/
static void uv_from_jitter_v2(float uv[2])
{
uv[0] += 0.5f;
uv[1] += 0.5f;
if (uv[0] + uv[1] > 1.0f) {
uv[0] = 1.0f - uv[0];
uv[1] = 1.0f - uv[1];
}
clamp_v2(uv, 0.0f, 1.0f);
}
BLI_INLINE float thickness_remap(float fac, float min, float max, float minmax_irange)
{
/* important not '<=' */
if (fac < max) {
fac = (fac - min) * minmax_irange;
fac = 1.0f - fac;
CLAMP(fac, 0.0f, 1.0f);
}
else {
fac = -1.0f;
}
return fac;
}
static void statvis_calc_thickness(const MeshRenderData &mr,
const float4x4 &object_to_world,
MutableSpan<float> r_thickness)
{
const float eps_offset = 0.00002f; /* values <= 0.00001 give errors */
/* cheating to avoid another allocation */
float *face_dists = r_thickness.data() + (mr.corners_num - mr.faces_num);
const float scale = 1.0f / mat4_to_scale(object_to_world.ptr());
const MeshStatVis *statvis = &mr.toolsettings->statvis;
const float min = statvis->thickness_min * scale;
const float max = statvis->thickness_max * scale;
const float minmax_irange = 1.0f / (max - min);
const int samples = statvis->thickness_samples;
float jit_ofs[32][2];
BLI_assert(samples <= 32);
BLI_assert(min <= max);
std::fill_n(face_dists, mr.faces_num, max);
BLI_jitter_init(jit_ofs, samples);
for (int j = 0; j < samples; j++) {
uv_from_jitter_v2(jit_ofs[j]);
}
if (mr.extract_type == MeshExtractType::BMesh) {
BMEditMesh *em = mr.edit_bmesh;
BMesh *bm = em->bm;
BM_mesh_elem_index_ensure(bm, BM_FACE);
BMBVHTree *bmtree = BKE_bmbvh_new_from_editmesh(em, 0, nullptr, false);
const Span<std::array<BMLoop *, 3>> looptris = em->looptris;
for (int i = 0; i < mr.corner_tris_num; i++) {
const BMLoop *const *ltri = looptris[i].data();
const int index = BM_elem_index_get(ltri[0]->f);
const float *cos[3] = {
bm_vert_co_get(mr, ltri[0]->v),
bm_vert_co_get(mr, ltri[1]->v),
bm_vert_co_get(mr, ltri[2]->v),
};
float ray_co[3];
float ray_no[3];
normal_tri_v3(ray_no, cos[2], cos[1], cos[0]);
for (int j = 0; j < samples; j++) {
float dist = face_dists[index];
interp_v3_v3v3v3_uv(ray_co, cos[0], cos[1], cos[2], jit_ofs[j]);
madd_v3_v3fl(ray_co, ray_no, eps_offset);
BMFace *f_hit = BKE_bmbvh_ray_cast(bmtree, ray_co, ray_no, 0.0f, &dist, nullptr, nullptr);
if (f_hit && dist < face_dists[index]) {
float angle_fac = fabsf(
dot_v3v3(bm_face_no_get(mr, ltri[0]->f), bm_face_no_get(mr, f_hit)));
angle_fac = 1.0f - angle_fac;
angle_fac = angle_fac * angle_fac * angle_fac;
angle_fac = 1.0f - angle_fac;
dist /= angle_fac;
face_dists[index] = std::min(dist, face_dists[index]);
}
}
}
BKE_bmbvh_free(bmtree);
BMIter iter;
BMFace *f;
int l_index = 0;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
float fac = face_dists[BM_elem_index_get(f)];
fac = thickness_remap(fac, min, max, minmax_irange);
for (int i = 0; i < f->len; i++, l_index++) {
r_thickness[l_index] = fac;
}
}
}
else {
bke::BVHTreeFromMesh treeData = mr.mesh->bvh_corner_tris();
const BVHTree *tree = treeData.tree;
if (tree == nullptr) {
return;
}
const Span<int3> corner_tris = mr.mesh->corner_tris();
const Span<int> tri_faces = mr.mesh->corner_tri_faces();
for (const int i : corner_tris.index_range()) {
const int index = tri_faces[i];
const float *cos[3] = {mr.vert_positions[mr.corner_verts[corner_tris[i][0]]],
mr.vert_positions[mr.corner_verts[corner_tris[i][1]]],
mr.vert_positions[mr.corner_verts[corner_tris[i][2]]]};
float ray_co[3];
float ray_no[3];
normal_tri_v3(ray_no, cos[2], cos[1], cos[0]);
for (int j = 0; j < samples; j++) {
interp_v3_v3v3v3_uv(ray_co, cos[0], cos[1], cos[2], jit_ofs[j]);
madd_v3_v3fl(ray_co, ray_no, eps_offset);
BVHTreeRayHit hit;
hit.index = -1;
hit.dist = face_dists[index];
if ((BLI_bvhtree_ray_cast(
tree, ray_co, ray_no, 0.0f, &hit, treeData.raycast_callback, &treeData) != -1) &&
hit.dist < face_dists[index])
{
float angle_fac = fabsf(dot_v3v3(mr.face_normals[index], hit.no));
angle_fac = 1.0f - angle_fac;
angle_fac = angle_fac * angle_fac * angle_fac;
angle_fac = 1.0f - angle_fac;
hit.dist /= angle_fac;
face_dists[index] = std::min(hit.dist, face_dists[index]);
}
}
}
for (const int face_i : mr.faces.index_range()) {
float fac = face_dists[face_i];
fac = thickness_remap(fac, min, max, minmax_irange);
for (const int loop_i : mr.faces[face_i]) {
r_thickness[loop_i] = fac;
}
}
}
}
struct BVHTree_OverlapData {
Span<float3> positions;
Span<int> corner_verts;
Span<int3> corner_tris;
Span<int> tri_faces;
float epsilon;
};
static bool bvh_overlap_cb(void *userdata, int index_a, int index_b, int /*thread*/)
{
BVHTree_OverlapData *data = static_cast<BVHTree_OverlapData *>(userdata);
if (UNLIKELY(data->tri_faces[index_a] == data->tri_faces[index_b])) {
return false;
}
const int3 tri_a = data->corner_tris[index_a];
const int3 tri_b = data->corner_tris[index_b];
const float *tri_a_co[3] = {data->positions[data->corner_verts[tri_a[0]]],
data->positions[data->corner_verts[tri_a[1]]],
data->positions[data->corner_verts[tri_a[2]]]};
const float *tri_b_co[3] = {data->positions[data->corner_verts[tri_b[0]]],
data->positions[data->corner_verts[tri_b[1]]],
data->positions[data->corner_verts[tri_b[2]]]};
float ix_pair[2][3];
int verts_shared = 0;
verts_shared = (ELEM(tri_a_co[0], UNPACK3(tri_b_co)) + ELEM(tri_a_co[1], UNPACK3(tri_b_co)) +
ELEM(tri_a_co[2], UNPACK3(tri_b_co)));
/* if 2 points are shared, bail out */
if (verts_shared >= 2) {
return false;
}
return (isect_tri_tri_v3(UNPACK3(tri_a_co), UNPACK3(tri_b_co), ix_pair[0], ix_pair[1]) &&
/* if we share a vertex, check the intersection isn't a 'point' */
((verts_shared == 0) || (len_squared_v3v3(ix_pair[0], ix_pair[1]) > data->epsilon)));
}
static void statvis_calc_intersect(const MeshRenderData &mr, MutableSpan<float> r_intersect)
{
for (int l_index = 0; l_index < mr.corners_num; l_index++) {
r_intersect[l_index] = -1.0f;
}
if (mr.extract_type == MeshExtractType::BMesh) {
BMEditMesh *em = mr.edit_bmesh;
uint overlap_len;
BMesh *bm = em->bm;
BM_mesh_elem_index_ensure(bm, BM_FACE);
BMBVHTree *bmtree = BKE_bmbvh_new_from_editmesh(em, 0, nullptr, false);
BVHTreeOverlap *overlap = BKE_bmbvh_overlap_self(bmtree, &overlap_len);
if (overlap) {
for (int i = 0; i < overlap_len; i++) {
BMFace *f_hit_pair[2] = {
em->looptris[overlap[i].indexA][0]->f,
em->looptris[overlap[i].indexB][0]->f,
};
for (int j = 0; j < 2; j++) {
BMFace *f_hit = f_hit_pair[j];
BMLoop *l_first = BM_FACE_FIRST_LOOP(f_hit);
int l_index = BM_elem_index_get(l_first);
for (int k = 0; k < f_hit->len; k++, l_index++) {
r_intersect[l_index] = 1.0f;
}
}
}
MEM_delete(overlap);
}
BKE_bmbvh_free(bmtree);
}
else {
uint overlap_len;
bke::BVHTreeFromMesh treeData = mr.mesh->bvh_corner_tris();
const BVHTree *tree = treeData.tree;
if (tree == nullptr) {
return;
}
BVHTree_OverlapData data = {};
data.positions = mr.vert_positions;
data.corner_verts = mr.corner_verts;
data.corner_tris = mr.mesh->corner_tris();
data.tri_faces = mr.mesh->corner_tri_faces();
data.epsilon = BLI_bvhtree_get_epsilon(tree);
BVHTreeOverlap *overlap = BLI_bvhtree_overlap_self(tree, &overlap_len, bvh_overlap_cb, &data);
if (overlap) {
for (int i = 0; i < overlap_len; i++) {
for (const IndexRange f_hit : {mr.faces[data.tri_faces[overlap[i].indexA]],
mr.faces[data.tri_faces[overlap[i].indexB]]})
{
int l_index = f_hit.start();
for (int k = 0; k < f_hit.size(); k++, l_index++) {
r_intersect[l_index] = 1.0f;
}
}
}
MEM_delete(overlap);
}
}
}
BLI_INLINE float distort_remap(float fac, float min, float /*max*/, float minmax_irange)
{
if (fac >= min) {
fac = (fac - min) * minmax_irange;
CLAMP(fac, 0.0f, 1.0f);
}
else {
/* fallback */
fac = -1.0f;
}
return fac;
}
static void statvis_calc_distort(const MeshRenderData &mr, MutableSpan<float> r_distort)
{
const MeshStatVis *statvis = &mr.toolsettings->statvis;
const float min = statvis->distort_min;
const float max = statvis->distort_max;
const float minmax_irange = 1.0f / (max - min);
if (mr.extract_type == MeshExtractType::BMesh) {
BMEditMesh *em = mr.edit_bmesh;
BMIter iter;
BMesh *bm = em->bm;
BMFace *f;
int l_index = 0;
int f_index = 0;
BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, f_index) {
float fac = -1.0f;
if (f->len > 3) {
BMLoop *l_iter, *l_first;
fac = 0.0f;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const float *no_face;
float no_corner[3];
if (!mr.bm_vert_coords.is_empty()) {
no_face = mr.bm_face_normals[f_index];
BM_loop_calc_face_normal_safe_vcos(
l_iter,
no_face,
reinterpret_cast<const float (*)[3]>(mr.bm_vert_coords.data()),
no_corner);
}
else {
no_face = f->no;
BM_loop_calc_face_normal_safe(l_iter, no_corner);
}
/* simple way to detect (what is most likely) concave */
if (dot_v3v3(no_face, no_corner) < 0.0f) {
negate_v3(no_corner);
}
fac = max_ff(fac, angle_normalized_v3v3(no_face, no_corner));
} while ((l_iter = l_iter->next) != l_first);
fac *= 2.0f;
}
fac = distort_remap(fac, min, max, minmax_irange);
for (int i = 0; i < f->len; i++, l_index++) {
r_distort[l_index] = fac;
}
}
}
else {
for (const int face_index : mr.faces.index_range()) {
const IndexRange face = mr.faces[face_index];
float fac = -1.0f;
if (face.size() > 3) {
const float *f_no = mr.face_normals[face_index];
fac = 0.0f;
for (const int corner : face.drop_front(1)) {
const int corner_prev = bke::mesh::face_corner_prev(face, corner);
const int corner_next = bke::mesh::face_corner_next(face, corner);
float no_corner[3];
normal_tri_v3(no_corner,
mr.vert_positions[mr.corner_verts[corner_prev]],
mr.vert_positions[mr.corner_verts[corner]],
mr.vert_positions[mr.corner_verts[corner_next]]);
/* simple way to detect (what is most likely) concave */
if (dot_v3v3(f_no, no_corner) < 0.0f) {
negate_v3(no_corner);
}
fac = max_ff(fac, angle_normalized_v3v3(f_no, no_corner));
}
fac *= 2.0f;
}
fac = distort_remap(fac, min, max, minmax_irange);
for (const int corner : face) {
r_distort[corner] = fac;
}
}
}
}
BLI_INLINE float sharp_remap(float fac, float min, float /*max*/, float minmax_irange)
{
/* important not '>=' */
if (fac > min) {
fac = (fac - min) * minmax_irange;
CLAMP(fac, 0.0f, 1.0f);
}
else {
/* fallback */
fac = -1.0f;
}
return fac;
}
static void statvis_calc_sharp(const MeshRenderData &mr, MutableSpan<float> r_sharp)
{
const MeshStatVis *statvis = &mr.toolsettings->statvis;
const float min = statvis->sharp_min;
const float max = statvis->sharp_max;
const float minmax_irange = 1.0f / (max - min);
/* Can we avoid this extra allocation? */
float *vert_angles = MEM_new_array_uninitialized<float>(mr.verts_num, __func__);
std::fill_n(vert_angles, mr.verts_num, -M_PI);
if (mr.extract_type == MeshExtractType::BMesh) {
BMEditMesh *em = mr.edit_bmesh;
BMIter iter;
BMesh *bm = em->bm;
BMFace *efa;
BMEdge *e;
/* first assign float values to verts */
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
float angle = BM_edge_calc_face_angle_signed(e);
float *col1 = &vert_angles[BM_elem_index_get(e->v1)];
float *col2 = &vert_angles[BM_elem_index_get(e->v2)];
*col1 = max_ff(*col1, angle);
*col2 = max_ff(*col2, angle);
}
/* Copy vert value to loops. */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
int l_index = BM_elem_index_get(l_iter);
int v_index = BM_elem_index_get(l_iter->v);
r_sharp[l_index] = sharp_remap(vert_angles[v_index], min, max, minmax_irange);
} while ((l_iter = l_iter->next) != l_first);
}
}
else {
/* first assign float values to verts */
Map<OrderedEdge, int> eh;
eh.reserve(mr.edges_num);
for (int face_index = 0; face_index < mr.faces_num; face_index++) {
const IndexRange face = mr.faces[face_index];
for (const int corner : face) {
const int vert_curr = mr.corner_verts[corner];
const int vert_next = mr.corner_verts[bke::mesh::face_corner_next(face, corner)];
float angle;
eh.add_or_modify(
{vert_curr, vert_next},
[&](int *value) { *value = face_index; },
[&](int *value) {
const int other_face_index = *value;
if (other_face_index == -1) {
/* non-manifold edge */
angle = DEG2RADF(90.0f);
return;
}
const float *f1_no = mr.face_normals[face_index];
const float *f2_no = mr.face_normals[other_face_index];
angle = angle_normalized_v3v3(f1_no, f2_no);
angle = is_edge_convex_v3(mr.vert_positions[vert_curr],
mr.vert_positions[vert_next],
f1_no,
f2_no) ?
angle :
-angle;
/* Tag as manifold. */
*value = -1;
});
float *col1 = &vert_angles[vert_curr];
float *col2 = &vert_angles[vert_next];
*col1 = max_ff(*col1, angle);
*col2 = max_ff(*col2, angle);
}
}
/* Remaining non manifold edges. */
for (const OrderedEdge &edge : eh.keys()) {
const float angle = DEG2RADF(90.0f);
float *col1 = &vert_angles[edge.v_low];
float *col2 = &vert_angles[edge.v_high];
*col1 = max_ff(*col1, angle);
*col2 = max_ff(*col2, angle);
}
for (int l_index = 0; l_index < mr.corners_num; l_index++) {
const int vert = mr.corner_verts[l_index];
r_sharp[l_index] = sharp_remap(vert_angles[vert], min, max, minmax_irange);
}
}
MEM_delete(vert_angles);
}
gpu::VertBufPtr extract_mesh_analysis(const MeshRenderData &mr, const float4x4 &object_to_world)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute("weight",
gpu::VertAttrType::SFLOAT_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
MutableSpan<float> vbo_data = vbo->data<float>();
switch (mr.toolsettings->statvis.type) {
case SCE_STATVIS_OVERHANG:
statvis_calc_overhang(mr, object_to_world, vbo_data);
break;
case SCE_STATVIS_THICKNESS:
statvis_calc_thickness(mr, object_to_world, vbo_data);
break;
case SCE_STATVIS_INTERSECT:
statvis_calc_intersect(mr, vbo_data);
break;
case SCE_STATVIS_DISTORT:
statvis_calc_distort(mr, vbo_data);
break;
case SCE_STATVIS_SHARP:
statvis_calc_sharp(mr, vbo_data);
break;
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,58 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "extract_mesh.hh"
namespace blender::draw {
gpu::VertBufPtr extract_orco(const MeshRenderData &mr)
{
const Span<float3> orco_data(
static_cast<const float3 *>(CustomData_get_layer(&mr.mesh->vert_data, CD_ORCO)),
mr.corners_num);
/* FIXME(fclem): We use the last component as a way to differentiate from generic vertex
* attributes. This is a substantial waste of video-ram and should be done another way.
* Unfortunately, at the time of writing, I did not found any other "non disruptive"
* alternative. */
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"orco", gpu::VertAttrType::SFLOAT_32_32_32_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
MutableSpan vbo_data = vbo->data<float4>();
const int64_t bytes = orco_data.size_in_bytes() + vbo_data.size_in_bytes();
threading::memory_bandwidth_bound_task(bytes, [&]() {
if (mr.extract_type == MeshExtractType::BMesh) {
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
vbo_data[index] = float4(orco_data[BM_elem_index_get(loop->v)], 0.0f);
loop = loop->next;
}
}
});
}
else {
const Span<int> corner_verts = mr.corner_verts;
threading::parallel_for(corner_verts.index_range(), 4096, [&](const IndexRange range) {
for (const int corner : range) {
vbo_data[corner] = float4(orco_data[corner_verts[corner]], 0.0f);
}
});
}
});
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,142 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static void extract_paint_overlay_flags(const MeshRenderData &mr, MutableSpan<int> flags)
{
const bool use_face_select = (mr.mesh->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
Span<bool> selection;
if (mr.mesh->editflag & ME_EDIT_PAINT_FACE_SEL) {
selection = mr.select_poly;
}
else if (mr.mesh->editflag & ME_EDIT_PAINT_VERT_SEL) {
selection = mr.select_vert;
}
if (selection.is_empty() && mr.hide_poly.is_empty() && (!mr.edit_bmesh || !mr.orig_index_vert)) {
flags.fill(0);
return;
}
const OffsetIndices faces = mr.faces;
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
if (selection.is_empty()) {
flags.fill(0);
}
else {
if (use_face_select) {
for (const int face : range) {
flags.slice(faces[face]).fill(selection[face] ? 1 : 0);
}
}
else {
const Span<int> corner_verts = mr.corner_verts;
for (const int face : range) {
for (const int corner : faces[face]) {
flags[corner] = selection[corner_verts[corner]] ? 1 : 0;
}
}
}
}
if (!mr.hide_poly.is_empty()) {
const Span<bool> hide_poly = mr.hide_poly;
for (const int face : range) {
if (hide_poly[face]) {
flags.slice(faces[face]).fill(-1);
}
}
}
if (mr.edit_bmesh && mr.orig_index_vert) {
const Span<int> corner_verts = mr.corner_verts;
const Span<int> orig_indices(mr.orig_index_vert, mr.verts_num);
for (const int face : range) {
for (const int corner : faces[face]) {
if (orig_indices[corner_verts[corner]] == ORIGINDEX_NONE) {
flags[corner] = -1;
}
}
}
}
});
}
static void extract_edit_flags_bm(const MeshRenderData &mr, MutableSpan<int> flags)
{
/* TODO: Return early if there are no hidden faces. */
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
if (BM_elem_flag_test(&face, BM_ELEM_HIDDEN)) {
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
flags.slice(face_range).fill(-1);
}
}
});
}
static const GPUVertFormat &get_paint_overlay_flag_format()
{
static const GPUVertFormat format = GPU_vertformat_from_attribute("paint_overlay_flag",
gpu::VertAttrType::SINT_32);
return format;
}
gpu::VertBufPtr extract_paint_overlay_flags(const MeshRenderData &mr)
{
const int size = mr.corners_num + mr.loose_indices_num;
gpu::VertBufPtr vbo = gpu::VertBufPtr(
GPU_vertbuf_create_with_format(get_paint_overlay_flag_format()));
GPU_vertbuf_data_alloc(*vbo, size);
MutableSpan vbo_data = vbo->data<int>();
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_data = vbo_data.take_back(mr.loose_indices_num);
if (mr.extract_type == MeshExtractType::Mesh) {
extract_paint_overlay_flags(mr, corners_data);
}
else {
extract_edit_flags_bm(mr, corners_data);
}
loose_data.fill(0);
return vbo;
}
static void update_loose_flags(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
gpu::VertBuf &flags)
{
const int vbo_size = subdiv_full_vbo_size(mr, subdiv_cache);
const int loose_geom_start = subdiv_cache.num_subdiv_loops;
/* Push VBO content to the GPU and bind the VBO so that #GPU_vertbuf_update_sub can work. */
GPU_vertbuf_use(&flags);
/* Default to zeroed attribute. The overlay shader should expect this and render engines should
* never draw loose geometry. */
const int default_value = 0;
for (const int i : IndexRange::from_begin_end(loose_geom_start, vbo_size)) {
/* TODO(fclem): This has HORRENDOUS performance. Prefer clearing the buffer on device with
* something like glClearBufferSubData. */
GPU_vertbuf_update_sub(&flags, i * sizeof(int), sizeof(int), &default_value);
}
}
gpu::VertBufPtr extract_paint_overlay_flags_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
gpu::VertBufPtr flags = gpu::VertBufPtr(GPU_vertbuf_create_on_device(
get_paint_overlay_flag_format(), subdiv_full_vbo_size(mr, subdiv_cache)));
draw_subdiv_build_paint_overlay_flag_buffer(subdiv_cache, *flags);
update_loose_flags(mr, subdiv_cache, *flags);
return flags;
}
} // namespace blender::draw

View File

@@ -0,0 +1,158 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "BLI_math_vector.h"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static void extract_positions_mesh(const MeshRenderData &mr, MutableSpan<float3> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
threading::memory_bandwidth_bound_task(
mr.vert_positions.size_in_bytes() + mr.corner_verts.size_in_bytes() +
vbo_data.size_in_bytes() + mr.loose_edges.size(),
[&]() {
array_utils::gather(mr.vert_positions, mr.corner_verts, corners_data);
extract_mesh_loose_edge_data(mr.vert_positions, mr.edges, mr.loose_edges, loose_edge_data);
array_utils::gather(mr.vert_positions, mr.loose_verts, loose_vert_data);
});
}
static void extract_positions_bm(const MeshRenderData &mr, MutableSpan<float3> vbo_data)
{
const BMesh &bm = *mr.bm;
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
corners_data[index] = bm_vert_co_get(mr, loop->v);
loop = loop->next;
}
}
});
mr.loose_edges.foreach_index(
[&](const int i, const int pos) {
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), i);
loose_edge_data[pos * 2 + 0] = bm_vert_co_get(mr, edge.v1);
loose_edge_data[pos * 2 + 1] = bm_vert_co_get(mr, edge.v2);
},
exec_mode::grain_size(4096));
mr.loose_verts.foreach_index(
[&](const int i, const int pos) {
const BMVert &vert = *BM_vert_at_index(&const_cast<BMesh &>(bm), i);
loose_vert_data[pos] = bm_vert_co_get(mr, &vert);
},
exec_mode::grain_size(2048));
}
gpu::VertBufPtr extract_positions(const MeshRenderData &mr)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"pos", gpu::VertAttrType::SFLOAT_32_32_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num + mr.loose_indices_num);
MutableSpan vbo_data = vbo->data<float3>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_positions_mesh(mr, vbo_data);
}
else {
extract_positions_bm(mr, vbo_data);
}
return vbo;
}
static void extract_loose_positions_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::VertBuf &vbo)
{
const IndexMask &loose_verts = mr.loose_verts;
const int loose_edges_num = mr.loose_edges.size();
if (loose_verts.is_empty() && loose_edges_num == 0) {
return;
}
/* Make sure buffer is active for sending loose data. */
GPU_vertbuf_use(&vbo);
const int resolution = subdiv_cache.resolution;
const Span<float3> cached_positions = subdiv_cache.loose_edge_positions;
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
const int edges_per_edge = subdiv_edges_per_coarse_edge(subdiv_cache);
const int loose_geom_start = subdiv_cache.num_subdiv_loops;
float3 edge_data[2];
for (const int i : IndexRange(loose_edges_num)) {
const int edge_offset = loose_geom_start + i * verts_per_edge;
const Span<float3> positions = cached_positions.slice(i * resolution, resolution);
for (const int edge : IndexRange(edges_per_edge)) {
edge_data[0] = positions[edge + 0];
edge_data[1] = positions[edge + 1];
GPU_vertbuf_update_sub(
&vbo, (edge_offset + edge * 2) * sizeof(float3), sizeof(float3) * 2, &edge_data);
}
}
const int loose_verts_start = loose_geom_start + loose_edges_num * verts_per_edge;
const Span<float3> positions = mr.vert_positions;
loose_verts.foreach_index([&](const int i, const int pos) {
GPU_vertbuf_update_sub(
&vbo, (loose_verts_start + pos) * sizeof(float3), sizeof(float3), &positions[i]);
});
}
gpu::VertBufPtr extract_positions_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
gpu::VertBufPtr *orco_vbo)
{
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"pos", gpu::VertAttrType::SFLOAT_32_32_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(format, subdiv_full_vbo_size(mr, subdiv_cache)));
if (subdiv_cache.num_subdiv_loops == 0) {
extract_loose_positions_subdiv(subdiv_cache, mr, *vbo);
return vbo;
}
if (orco_vbo) {
/* FIXME(fclem): We use the last component as a way to differentiate from generic vertex
* attributes. This is a substantial waste of video-ram and should be done another way.
* Unfortunately, at the time of writing, I did not found any other "non disruptive"
* alternative. */
static const GPUVertFormat format = GPU_vertformat_from_attribute(
"orco", gpu::VertAttrType::SFLOAT_32_32_32_32);
*orco_vbo = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(format, subdiv_cache.num_subdiv_loops));
}
draw_subdiv_extract_pos(subdiv_cache, vbo.get(), orco_vbo ? orco_vbo->get() : nullptr);
extract_loose_positions_subdiv(subdiv_cache, mr, *vbo);
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,187 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "BLI_math_vector.h"
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "BKE_paint.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
namespace blender::draw {
static const GPUVertFormat &get_sculpt_data_format()
{
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "fset", gpu::VertAttrType::UNORM_8_8_8_8);
GPU_vertformat_attr_add(&format, "msk", gpu::VertAttrType::SFLOAT_32);
return format;
}();
return format;
}
gpu::VertBufPtr extract_sculpt_data(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(get_sculpt_data_format()));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
struct gpuSculptData {
uchar4 face_set_color;
float mask;
};
MutableSpan vbo_data = vbo->data<gpuSculptData>();
const int default_face_set = mr.mesh->face_sets_color_default;
const int face_set_seed = mr.mesh->face_sets_color_seed;
if (mr.extract_type == MeshExtractType::BMesh) {
const BMesh &bm = *mr.bm;
const int mask_offset = CustomData_get_offset_named(
&mr.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
const int face_set_offset = CustomData_get_offset_named(
&mr.bm->pdata, CD_PROP_INT32, ".sculpt_face_set");
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
uchar4 face_set_color(UCHAR_MAX);
if (face_set_offset != -1) {
const int face_set_id = BM_ELEM_CD_GET_INT(&face, face_set_offset);
if (face_set_id != default_face_set) {
BKE_paint_face_set_overlay_color_get(face_set_id, face_set_seed, face_set_color);
}
}
vbo_data.slice(face_range).fill(gpuSculptData{face_set_color, 0.0f});
if (mask_offset != -1) {
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
vbo_data[index].mask = BM_ELEM_CD_GET_FLOAT(loop->v, mask_offset);
loop = loop->next;
}
}
}
});
}
else {
const OffsetIndices faces = mr.faces;
const Span<int> corner_verts = mr.corner_verts;
const bke::AttributeAccessor attributes = mr.mesh->attributes();
const VArraySpan mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point);
const VArraySpan face_set = *attributes.lookup<int>(".sculpt_face_set", bke::AttrDomain::Face);
threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
for (const int face_index : range) {
const IndexRange face = faces[face_index];
uchar4 face_set_color(UCHAR_MAX);
if (!face_set.is_empty()) {
const int face_set_id = face_set[face_index];
if (face_set_id != default_face_set) {
BKE_paint_face_set_overlay_color_get(face_set_id, face_set_seed, face_set_color);
}
}
vbo_data.slice(face).fill(gpuSculptData{face_set_color, 0.0f});
if (!mask.is_empty()) {
for (const int corner : face) {
vbo_data[corner].mask = mask[corner_verts[corner]];
}
}
}
});
}
return vbo;
}
gpu::VertBufPtr extract_sculpt_data_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache)
{
const Mesh &coarse_mesh = *mr.mesh;
const int subdiv_corners_num = subdiv_cache.num_subdiv_loops;
/* First, interpolate mask if available. */
gpu::VertBuf *mask_vbo = nullptr;
gpu::VertBuf *subdiv_mask_vbo = nullptr;
const bke::AttributeAccessor attributes = coarse_mesh.attributes();
if (const VArray mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point)) {
GPUVertFormat mask_format = {0};
GPU_vertformat_attr_add(&mask_format, "msk", gpu::VertAttrType::SFLOAT_32);
const Span<int> corner_verts = coarse_mesh.corner_verts();
mask_vbo = GPU_vertbuf_calloc();
GPU_vertbuf_init_with_format(*mask_vbo, mask_format);
GPU_vertbuf_data_alloc(*mask_vbo, corner_verts.size());
MutableSpan mask_vbo_data = mask_vbo->data<float>();
array_utils::gather(mask, corner_verts, mask_vbo_data);
subdiv_mask_vbo = GPU_vertbuf_calloc();
GPU_vertbuf_init_build_on_device(*subdiv_mask_vbo, mask_format, subdiv_corners_num);
draw_subdiv_interp_custom_data(subdiv_cache, *mask_vbo, *subdiv_mask_vbo, GPU_COMP_F32, 1, 0);
}
/* Then, gather face sets. */
GPUVertFormat face_set_format = {0};
GPU_vertformat_attr_add(&face_set_format, "msk", gpu::VertAttrType::UNORM_8_8_8_8);
gpu::VertBuf *face_set_vbo = GPU_vertbuf_calloc();
GPU_vertbuf_init_with_format(*face_set_vbo, face_set_format);
GPU_vertbuf_data_alloc(*face_set_vbo, subdiv_corners_num);
struct gpuFaceSet {
uchar4 color;
};
MutableSpan face_set_vbo_data = face_set_vbo->data<gpuFaceSet>();
const VArraySpan face_sets = *attributes.lookup<int>(".sculpt_face_set", bke::AttrDomain::Face);
if (face_sets.is_empty()) {
face_set_vbo_data.fill({uchar4{UCHAR_MAX}});
}
else {
const Span<int> subdiv_loop_face_index(subdiv_cache.subdiv_loop_face_index,
subdiv_corners_num);
const int default_face_set = coarse_mesh.face_sets_color_default;
const int face_set_seed = coarse_mesh.face_sets_color_seed;
threading::parallel_for(IndexRange(subdiv_corners_num), 4096, [&](const IndexRange range) {
for (const int i : range) {
const int face_index = subdiv_loop_face_index[i];
uchar4 face_set_color(UCHAR_MAX);
const int face_set_id = face_sets[face_index];
/* Skip for the default color face set to render it white. */
if (face_set_id != default_face_set) {
BKE_paint_face_set_overlay_color_get(face_set_id, face_set_seed, face_set_color);
}
copy_v3_v3_uchar(face_set_vbo_data[i].color, face_set_color);
}
});
}
/* Finally, interleave mask and face sets. */
gpu::VertBufPtr vbo = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(get_sculpt_data_format(), subdiv_corners_num));
draw_subdiv_build_sculpt_data_buffer(subdiv_cache, subdiv_mask_vbo, face_set_vbo, vbo.get());
if (mask_vbo) {
GPU_vertbuf_discard(mask_vbo);
GPU_vertbuf_discard(subdiv_mask_vbo);
}
GPU_vertbuf_discard(face_set_vbo);
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,324 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
namespace blender::draw {
static gpu::VertBufPtr create_vbo(const int size)
{
static GPUVertFormat format = GPU_vertformat_from_attribute("index", gpu::VertAttrType::SINT_32);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, size);
return vbo;
}
/* TODO: Use #glVertexID to get loop index and use the data structure on the CPU to retrieve the
* select element associated with this loop ID. This would remove the need for this separate
* index VBO's. We could upload the p/e/orig_index_vert as a buffer texture and sample it inside
* the shader to output original index. */
static void extract_vert_index_mesh(const MeshRenderData &mr, MutableSpan<int> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
if (mr.orig_index_vert) {
const Span<int> orig_index_vert(mr.orig_index_vert, mr.verts_num);
array_utils::gather(orig_index_vert, mr.corner_verts, corners_data);
extract_mesh_loose_edge_data(orig_index_vert, mr.edges, mr.loose_edges, loose_edge_data);
array_utils::gather(orig_index_vert, mr.loose_verts, loose_vert_data);
}
else {
array_utils::copy(mr.corner_verts, corners_data);
const Span<int2> edges = mr.edges;
mr.loose_edges.foreach_index_optimized<int>(
[&](const int i, const int pos) {
loose_edge_data[pos * 2 + 0] = edges[i][0];
loose_edge_data[pos * 2 + 1] = edges[i][1];
},
exec_mode::grain_size(4096));
mr.loose_verts.to_indices(loose_vert_data);
}
}
static void extract_vert_index_bm(const MeshRenderData &mr, MutableSpan<int> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
corners_data[index] = BM_elem_index_get(loop->v);
loop = loop->next;
}
}
});
mr.loose_edges.foreach_index(
[&](const int i, const int pos) {
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), i);
loose_edge_data[pos * 2 + 0] = BM_elem_index_get(edge.v1);
loose_edge_data[pos * 2 + 1] = BM_elem_index_get(edge.v2);
},
exec_mode::grain_size(4096));
mr.loose_verts.to_indices(loose_vert_data);
}
gpu::VertBufPtr extract_vert_index(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = create_vbo(mr.corners_num + mr.loose_indices_num);
if (mr.extract_type == MeshExtractType::Mesh) {
extract_vert_index_mesh(mr, vbo->data<int>());
}
else {
extract_vert_index_bm(mr, vbo->data<int>());
}
return vbo;
}
static void extract_edge_index_mesh(const MeshRenderData &mr, MutableSpan<int> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
if (mr.orig_index_edge) {
const Span<int> orig_index_edge(mr.orig_index_edge, mr.edges_num);
array_utils::gather(orig_index_edge, mr.corner_edges, corners_data);
mr.loose_edges.foreach_index_optimized<int>(
[&](const int i, const int pos) {
loose_edge_data[pos * 2 + 0] = orig_index_edge[i];
loose_edge_data[pos * 2 + 1] = orig_index_edge[i];
},
exec_mode::grain_size(4096));
}
else {
array_utils::copy(mr.corner_edges, corners_data);
mr.loose_edges.foreach_index_optimized<int>(
[&](const int i, const int pos) {
loose_edge_data[pos * 2 + 0] = i;
loose_edge_data[pos * 2 + 1] = i;
},
exec_mode::grain_size(4096));
}
}
static void extract_edge_index_bm(const MeshRenderData &mr, MutableSpan<int> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
corners_data[index] = BM_elem_index_get(loop->e);
loop = loop->next;
}
}
});
mr.loose_edges.foreach_index_optimized<int>(
[&](const int i, const int pos) {
loose_edge_data[pos * 2 + 0] = i;
loose_edge_data[pos * 2 + 1] = i;
},
exec_mode::grain_size(4096));
}
gpu::VertBufPtr extract_edge_index(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = create_vbo(mr.corners_num + mr.loose_edges.size() * 2);
if (mr.extract_type == MeshExtractType::Mesh) {
extract_edge_index_mesh(mr, vbo->data<int>());
}
else {
extract_edge_index_bm(mr, vbo->data<int>());
}
return vbo;
}
static void extract_face_index_mesh(const MeshRenderData &mr, MutableSpan<int> vbo_data)
{
const OffsetIndices faces = mr.faces;
if (mr.orig_index_face) {
const Span<int> orig_index_face(mr.orig_index_face, mr.edges_num);
threading::parallel_for(faces.index_range(), 2048, [&](const IndexRange range) {
for (const int face : range) {
vbo_data.slice(faces[face]).fill(orig_index_face[face]);
}
});
}
else {
offset_indices::build_reverse_map(faces, vbo_data);
}
}
static void extract_face_index_bm(const MeshRenderData &mr, MutableSpan<int> vbo_data)
{
const BMesh &bm = *mr.bm;
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const IndexRange face_range(BM_elem_index_get(BM_FACE_FIRST_LOOP(&face)), face.len);
vbo_data.slice(face_range).fill(face_index);
}
});
}
gpu::VertBufPtr extract_face_index(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = create_vbo(mr.corners_num);
if (mr.extract_type == MeshExtractType::Mesh) {
extract_face_index_mesh(mr, vbo->data<int>());
}
else {
extract_face_index_bm(mr, vbo->data<int>());
}
return vbo;
}
static void extract_vert_idx_loose_geom_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
MutableSpan<int32_t> vbo_data)
{
const IndexMask &loose_verts = mr.loose_verts;
const IndexMask &loose_edges = mr.loose_edges;
if (loose_edges.is_empty() && loose_verts.is_empty()) {
return;
}
const Span<int2> coarse_edges = mr.edges;
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
MutableSpan<int32_t> edge_data = vbo_data.slice(subdiv_cache.num_subdiv_loops,
loose_edges.size() * verts_per_edge);
loose_edges.foreach_index([&](const int i, const int pos) {
const int2 edge = coarse_edges[i];
MutableSpan data = edge_data.slice(pos * verts_per_edge, verts_per_edge);
data.first() = mr.orig_index_vert ? mr.orig_index_vert[edge[0]] : edge[0];
data.last() = mr.orig_index_vert ? mr.orig_index_vert[edge[1]] : edge[1];
});
MutableSpan<int32_t> loose_vert_data = vbo_data.take_back(loose_verts.size());
if (mr.orig_index_vert) {
array_utils::gather(Span(mr.orig_index_vert, mr.verts_num), loose_verts, loose_vert_data);
}
else {
loose_verts.to_indices(loose_vert_data);
}
}
gpu::VertBufPtr extract_vert_index_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr)
{
const int loose_len = subdiv_loose_edges_num(mr, subdiv_cache) * 2 + mr.loose_verts.size();
/* Each element points to an element in the `ibo.points`. */
gpu::VertBufPtr vbo = draw_subdiv_init_origindex_buffer(
subdiv_cache.verts_orig_index->data<int32_t>().data(),
subdiv_cache.num_subdiv_loops,
loose_len);
if (!mr.orig_index_vert) {
return vbo;
}
/* Remap the vertex indices to those pointed by the origin indices layer. At this point, the
* VBO data is a copy of #verts_orig_index which contains the coarse vertices indices, so
* the memory can both be accessed for lookup and immediately overwritten. */
MutableSpan vbo_data = vbo->data<int32_t>();
for (int i = 0; i < subdiv_cache.num_subdiv_loops; i++) {
if (vbo_data[i] == -1) {
continue;
}
vbo_data[i] = mr.orig_index_vert[vbo_data[i]];
}
extract_vert_idx_loose_geom_subdiv(subdiv_cache, mr, vbo_data);
return vbo;
}
static void extract_edge_idx_loose_geom_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr,
MutableSpan<int32_t> vbo_data)
{
const IndexMask &loose_edges = mr.loose_edges;
if (loose_edges.is_empty()) {
return;
}
const int verts_per_edge = subdiv_verts_per_coarse_edge(subdiv_cache);
MutableSpan data = vbo_data.slice(subdiv_cache.num_subdiv_loops,
loose_edges.size() * verts_per_edge);
loose_edges.foreach_index([&](const int edge, const int i) {
const int index = mr.orig_index_edge ? mr.orig_index_edge[edge] : edge;
data.slice(i * verts_per_edge, verts_per_edge).fill(index);
});
}
gpu::VertBufPtr extract_edge_index_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = draw_subdiv_init_origindex_buffer(
subdiv_cache.edges_orig_index->data<int32_t>().data(),
subdiv_cache.num_subdiv_loops,
subdiv_loose_edges_num(mr, subdiv_cache) * 2);
extract_edge_idx_loose_geom_subdiv(subdiv_cache, mr, vbo->data<int32_t>());
return vbo;
}
gpu::VertBufPtr extract_face_index_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = draw_subdiv_init_origindex_buffer(
subdiv_cache.subdiv_loop_face_index, subdiv_cache.num_subdiv_loops, 0);
if (!mr.orig_index_face) {
return vbo;
}
/* Remap the face indices to those pointed by the origin indices layer. At this point, the
* VBO data is a copy of #subdiv_loop_face_index which contains the coarse face indices, so
* the memory can both be accessed for lookup and immediately overwritten. */
MutableSpan vbo_data = vbo->data<int32_t>();
for (int i = 0; i < subdiv_cache.num_subdiv_loops; i++) {
vbo_data[i] = mr.orig_index_face[vbo_data[i]];
}
return vbo;
}
gpu::VertBufPtr extract_face_dot_index(const MeshRenderData &mr)
{
gpu::VertBufPtr vbo = create_vbo(mr.faces_num);
if (mr.extract_type == MeshExtractType::Mesh) {
if (mr.orig_index_face) {
const Span<int> orig_index_face(mr.orig_index_face, mr.faces_num);
array_utils::copy(orig_index_face, vbo->data<int>());
}
else {
array_utils::fill_index_range(vbo->data<int>());
}
}
else {
array_utils::fill_index_range(vbo->data<int>());
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "DNA_meshdata_types.h"
#include "extract_mesh.hh"
namespace blender::draw {
struct SkinRootData {
float size;
float3 local_pos;
};
gpu::VertBufPtr extract_skin_roots(const MeshRenderData &mr)
{
/* Exclusively for edit mode. */
BLI_assert(mr.bm);
static const GPUVertFormat format = []() {
GPUVertFormat format{};
GPU_vertformat_attr_add(&format, "size", gpu::VertAttrType::SFLOAT_32);
GPU_vertformat_attr_add(&format, "local_pos", gpu::VertAttrType::SFLOAT_32_32_32);
return format;
}();
Vector<SkinRootData> skin_roots;
const int offset = CustomData_get_offset(&mr.bm->vdata, CD_MVERT_SKIN);
BMIter iter;
BMVert *vert;
BM_ITER_MESH (vert, &iter, mr.bm, BM_VERTS_OF_MESH) {
const MVertSkin *vs = static_cast<const MVertSkin *> BM_ELEM_CD_GET_VOID_P(vert, offset);
if (vs->flag & MVERT_SKIN_ROOT) {
skin_roots.append({(vs->radius[0] + vs->radius[1]) * 0.5f, bm_vert_co_get(mr, vert)});
}
}
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, skin_roots.size());
vbo->data<SkinRootData>().copy_from(skin_roots);
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,236 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include <climits>
#include "BLI_string.h"
#include "GPU_attribute_convert.hh"
#include "BKE_attribute.hh"
#include "BKE_editmesh_tangent.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_tangent.hh"
#include "extract_mesh.hh"
#include "draw_subdivision.hh"
namespace blender::draw {
static Array<Array<float4>> extract_tan_init_common(const MeshRenderData &mr,
const MeshBatchCache &cache,
GPUVertFormat *format,
gpu::VertAttrType gpu_attr_type)
{
GPU_vertformat_deinterleave(format);
VectorSet<std::string> tan_layers = cache.cd_used.tan;
bool use_orco_tan = cache.cd_used.tan_orco;
const StringRef active_name = mr.mesh->active_uv_map_name();
const StringRef default_name = mr.mesh->default_uv_map_name();
/* FIXME(#91838): This is to avoid a crash when orco tangent was requested but there are valid
* uv layers. It would be better to fix the root cause. */
if (tan_layers.is_empty() && use_orco_tan && !default_name.is_empty()) {
tan_layers.add(default_name);
use_orco_tan = false;
}
if (use_orco_tan) {
Array<float4> tangents;
if (mr.extract_type == MeshExtractType::BMesh) {
Array<float3> positions = BM_mesh_vert_coords_alloc(mr.bm);
tangents = BKE_editmesh_orco_tangents_calc(
mr.edit_bmesh, mr.bm_face_normals, mr.bm_loop_normals, positions);
}
else {
Span<float3> orco;
Array<float3> orco_allocated;
if (const float3 *orco_ptr = static_cast<const float3 *>(
CustomData_get_layer(&mr.mesh->vert_data, CD_ORCO)))
{
orco = Span(orco_ptr, mr.verts_num);
}
else {
orco_allocated = mr.vert_positions;
/* TODO: This is not thread-safe. Draw extraction should not modify the mesh. */
BKE_mesh_orco_verts_transform(const_cast<Mesh *>(mr.mesh), orco_allocated, false);
orco = orco_allocated;
}
tangents = bke::mesh::calc_orco_tangents(mr.vert_positions,
mr.faces,
mr.corner_verts,
mr.mesh->corner_tris(),
mr.mesh->corner_tri_faces(),
mr.sharp_faces,
mr.mesh->vert_normals(),
mr.face_normals,
mr.corner_normals,
orco);
}
char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
GPU_vertformat_safe_attr_name("orco", attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
SNPRINTF(attr_name, "t%s", attr_safe_name);
GPU_vertformat_attr_add(format, attr_name, gpu_attr_type);
GPU_vertformat_alias_add(format, "t");
GPU_vertformat_alias_add(format, "at");
return {std::move(tangents)};
}
Vector<StringRef> uv_names;
for (const StringRef name : tan_layers.as_span().take_front(MAX_MTFACE)) {
if (tan_layers.contains(name)) {
char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
/* Tangent layer name. */
SNPRINTF(attr_name, "t%s", attr_safe_name);
GPU_vertformat_attr_add(format, attr_name, gpu_attr_type);
/* Active render layer name. */
if (name == default_name) {
GPU_vertformat_alias_add(format, "t");
}
/* Active display layer name. */
if (name == active_name) {
GPU_vertformat_alias_add(format, "at");
}
uv_names.append(name);
}
}
if (uv_names.is_empty()) {
GPU_vertformat_attr_add(format, "dummy", gpu::VertAttrType::SFLOAT_32);
return {};
}
Array<Array<float4>> results;
if (mr.extract_type == MeshExtractType::BMesh) {
results = BKE_editmesh_uv_tangents_calc(
mr.edit_bmesh, mr.bm_face_normals, mr.bm_loop_normals, uv_names);
}
else {
Array<VArraySpan<float2>> uv_maps(uv_names.size());
Array<Span<float2>> uv_map_spans(uv_names.size());
const bke::AttributeAccessor attributes = mr.mesh->attributes();
for (const int i : uv_names.index_range()) {
uv_maps[i] = *attributes.lookup<float2>(uv_names[i], bke::AttrDomain::Corner);
uv_map_spans[i] = uv_maps[i];
}
results = bke::mesh::calc_uv_tangents(mr.vert_positions,
mr.faces,
mr.corner_verts,
mr.mesh->corner_tris(),
mr.mesh->corner_tri_faces(),
mr.sharp_faces,
mr.mesh->vert_normals(),
mr.face_normals,
mr.corner_normals,
uv_map_spans);
}
if (format->attr_len == 0) {
GPU_vertformat_attr_add(format, "dummy", gpu::VertAttrType::SFLOAT_32);
}
return results;
}
gpu::VertBufPtr extract_tangents(const MeshRenderData &mr,
const MeshBatchCache &cache,
const bool use_hq)
{
gpu::VertAttrType gpu_attr_type = use_hq ? gpu::VertAttrType::SNORM_16_16_16_16 :
gpu::VertAttrType::SNORM_10_10_10_2;
GPUVertFormat format = {0};
const Array<Array<float4>> tangents = extract_tan_init_common(mr, cache, &format, gpu_attr_type);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num);
if (use_hq) {
MutableSpan tan_data = vbo->data<short4>();
int vbo_index = 0;
for (const int i : tangents.index_range()) {
const Span<float4> layer_data = tangents[i];
for (int corner = 0; corner < mr.corners_num; corner++) {
tan_data[vbo_index] = gpu::convert_normal<short4>(float3(layer_data[corner]));
tan_data[vbo_index].w = (layer_data[corner][3] > 0.0f) ? SHRT_MAX : SHRT_MIN;
vbo_index++;
}
}
BLI_assert(vbo_index == tan_data.size());
}
else {
MutableSpan tan_data = vbo->data<gpu::PackedNormal>();
int vbo_index = 0;
for (const int i : tangents.index_range()) {
const Span<float4> layer_data = tangents[i];
for (int corner = 0; corner < mr.corners_num; corner++) {
tan_data[vbo_index] = gpu::convert_normal<gpu::PackedNormal>(float3(layer_data[corner]));
tan_data[vbo_index].w = (layer_data[corner][3] > 0.0f) ? 1 : -2;
vbo_index++;
}
}
BLI_assert(vbo_index == tan_data.size());
}
return vbo;
}
static const GPUVertFormat &get_coarse_tan_format()
{
static GPUVertFormat format = GPU_vertformat_from_attribute(
"tan", gpu::VertAttrType::SFLOAT_32_32_32_32);
return format;
}
gpu::VertBufPtr extract_tangents_subdiv(const MeshRenderData &mr,
const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache)
{
gpu::VertAttrType gpu_attr_type = gpu::VertAttrType::SFLOAT_32_32_32_32;
GPUVertFormat format = {0};
const Array<Array<float4>> tangents = extract_tan_init_common(mr, cache, &format, gpu_attr_type);
gpu::VertBufPtr vbo = gpu::VertBufPtr(
GPU_vertbuf_create_on_device(format, subdiv_cache.num_subdiv_loops));
gpu::VertBuf *coarse_vbo = GPU_vertbuf_calloc();
/* Dynamic as we upload and interpolate layers one at a time. */
GPU_vertbuf_init_with_format_ex(*coarse_vbo, get_coarse_tan_format(), GPU_USAGE_DYNAMIC);
GPU_vertbuf_data_alloc(*coarse_vbo, mr.corners_num);
/* Index of the tangent layer in the compact buffer. Used layers are stored in a single buffer.
*/
int pack_layer_index = 0;
for (const int i : tangents.index_range()) {
float4 *tan_data = coarse_vbo->data<float4>().data();
const Span<float4> values = tangents[i];
for (int corner = 0; corner < mr.corners_num; corner++) {
*tan_data = values[corner];
(*tan_data)[3] = (values[corner][3] > 0.0f) ? 1.0f : -1.0f;
tan_data++;
}
/* Ensure data is uploaded properly. */
GPU_vertbuf_tag_dirty(coarse_vbo);
/* Include stride in offset. */
const int dst_offset = int(subdiv_cache.num_subdiv_loops) * 4 * pack_layer_index++;
draw_subdiv_interp_custom_data(subdiv_cache, *coarse_vbo, *vbo, GPU_COMP_F32, 4, dst_offset);
}
GPU_vertbuf_discard(coarse_vbo);
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,175 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_utf8.h"
#include "BKE_anonymous_attribute_id.hh"
#include "BKE_attribute.hh"
#include "draw_subdivision.hh"
#include "extract_mesh.hh"
namespace blender::draw {
/* Initialize the vertex format to be used for UVs. Return true if any UV layer is
* found, false otherwise. */
static VectorSet<StringRef> mesh_extract_uv_format_init(GPUVertFormat *format,
const MeshBatchCache &cache,
const Mesh &mesh,
const MeshExtractType extract_type)
{
GPU_vertformat_deinterleave(format);
VectorSet<StringRef> uv_layers;
for (const StringRef name : cache.cd_used.uv.as_span().take_front(MAX_MTFACE)) {
uv_layers.add_new(name);
}
const StringRef active_name = mesh.active_or_default_uv_map_name();
const StringRef default_name = mesh.default_uv_map_name();
/* HACK to fix #68857 */
if (extract_type == MeshExtractType::BMesh && cache.cd_used.edit_uv == 1) {
if (!bke::attribute_name_is_anonymous(default_name)) {
uv_layers.add(default_name);
}
}
const StringRef stencil_name = [&]() -> StringRef {
StringRef name = mesh.stencil_uv_map_attribute;
if (name.is_empty()) {
name = mesh.active_uv_map_name();
}
return name;
}();
for (const StringRef name : uv_layers) {
char attr_name[32], attr_safe_name[GPU_MAX_SAFE_ATTR_NAME];
GPU_vertformat_safe_attr_name(name, attr_safe_name, GPU_MAX_SAFE_ATTR_NAME);
SNPRINTF_UTF8(attr_name, "a%s", attr_safe_name);
GPU_vertformat_attr_add(format, attr_name, gpu::VertAttrType::SFLOAT_32_32);
if (name == default_name) {
GPU_vertformat_alias_add(format, "a");
}
if (name == active_name) {
GPU_vertformat_alias_add(format, "au");
/* Alias to `pos` for edit uvs. */
GPU_vertformat_alias_add(format, "pos");
}
if (name == stencil_name) {
GPU_vertformat_alias_add(format, "mu");
}
}
if (format->attr_len == 0) {
GPU_vertformat_attr_add(format, "dummy", gpu::VertAttrType::SFLOAT_32_32);
}
return uv_layers;
}
gpu::VertBufPtr extract_uv_maps(const MeshRenderData &mr, const MeshBatchCache &cache)
{
GPUVertFormat format = {0};
int v_len = mr.corners_num;
const VectorSet<StringRef> uv_layers = mesh_extract_uv_format_init(
&format, cache, *mr.mesh, mr.extract_type);
if (uv_layers.is_empty()) {
/* VBO will not be used, only allocate minimum of memory. */
v_len = 1;
}
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, v_len);
MutableSpan<float2> uv_data = vbo->data<float2>();
threading::memory_bandwidth_bound_task(uv_data.size_in_bytes() * 2, [&]() {
if (mr.extract_type == MeshExtractType::BMesh) {
const BMesh &bm = *mr.bm;
for (const int i : uv_layers.index_range()) {
MutableSpan<float2> data = uv_data.slice(i * bm.totloop, bm.totloop);
const int offset = CustomData_get_offset_named(&bm.ldata, CD_PROP_FLOAT2, uv_layers[i]);
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
data[index] = BM_ELEM_CD_GET_FLOAT_P(loop, offset);
loop = loop->next;
}
}
});
}
}
else {
const bke::AttributeAccessor attributes = mr.mesh->attributes();
for (const int i : uv_layers.index_range()) {
const VArray uv_map = *attributes.lookup_or_default<float2>(
uv_layers[i], bke::AttrDomain::Corner, float2(0));
array_utils::copy(uv_map, uv_data.slice(i * mr.corners_num, mr.corners_num));
}
}
});
return vbo;
}
static VectorSet<StringRef> all_uv_map_attributes(const Mesh &mesh)
{
const bke::AttributeAccessor attributes = mesh.attributes();
VectorSet<StringRef> result;
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (iter.domain == bke::AttrDomain::Corner && iter.data_type == bke::AttrType::Float2) {
result.add(iter.name);
}
});
return result;
}
gpu::VertBufPtr extract_uv_maps_subdiv(const DRWSubdivCache &subdiv_cache,
const MeshBatchCache &cache)
{
const Mesh *coarse_mesh = subdiv_cache.mesh;
GPUVertFormat format = {0};
const VectorSet<StringRef> uv_layers = mesh_extract_uv_format_init(
&format, cache, *coarse_mesh, MeshExtractType::Mesh);
uint v_len = subdiv_cache.num_subdiv_loops;
if (uv_layers.is_empty()) {
/* TODO(kevindietrich): handle this more gracefully. */
v_len = 1;
}
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_on_device(format, v_len));
if (uv_layers.is_empty()) {
return vbo;
}
const VectorSet<StringRef> all_uv_maps = all_uv_map_attributes(*coarse_mesh);
/* Index of the UV layer in the compact buffer. Used UV layers are stored in a single buffer. */
for (const int pack_layer_index : uv_layers.index_range()) {
const StringRef name = uv_layers[pack_layer_index];
/* This lookup may fail because some modifiers (e.g. Skin)
* do not generate UV maps. See: #151787. */
const int i = all_uv_maps.index_of_try(name);
if (i == -1) {
continue;
}
const int offset = int(subdiv_cache.num_subdiv_loops) * pack_layer_index;
draw_subdiv_extract_uvs(subdiv_cache, vbo.get(), i, offset);
}
return vbo;
}
} // namespace blender::draw

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2021 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup draw
*/
#include "BLI_array_utils.hh"
#include "GPU_attribute_convert.hh"
#include "extract_mesh.hh"
namespace blender::draw {
static void extract_vert_normals_mesh(const MeshRenderData &mr,
MutableSpan<gpu::PackedNormal> vbo_data)
{
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
const Span<float3> vert_normals = mr.mesh->vert_normals();
Array<gpu::PackedNormal> converted(vert_normals.size());
convert_normals(vert_normals, converted.as_mutable_span());
static_assert(sizeof(gpu::PackedNormal) == sizeof(int32_t));
array_utils::gather(
converted.as_span().cast<int32_t>(), mr.corner_verts, corners_data.cast<int32_t>());
extract_mesh_loose_edge_data(converted.as_span(), mr.edges, mr.loose_edges, loose_edge_data);
array_utils::gather(
converted.as_span().cast<int32_t>(), mr.loose_verts, loose_vert_data.cast<int32_t>());
}
static void extract_vert_normals_bm(const MeshRenderData &mr,
MutableSpan<gpu::PackedNormal> vbo_data)
{
const BMesh &bm = *mr.bm;
MutableSpan corners_data = vbo_data.take_front(mr.corners_num);
MutableSpan loose_edge_data = vbo_data.slice(mr.corners_num, mr.loose_edges.size() * 2);
MutableSpan loose_vert_data = vbo_data.take_back(mr.loose_verts.size());
threading::parallel_for(IndexRange(bm.totface), 2048, [&](const IndexRange range) {
for (const int face_index : range) {
const BMFace &face = *BM_face_at_index(&const_cast<BMesh &>(bm), face_index);
const BMLoop *loop = BM_FACE_FIRST_LOOP(&face);
for ([[maybe_unused]] const int i : IndexRange(face.len)) {
const int index = BM_elem_index_get(loop);
corners_data[index] = gpu::convert_normal<gpu::PackedNormal>(bm_vert_no_get(mr, loop->v));
loop = loop->next;
}
}
});
mr.loose_edges.foreach_index(
[&](const int i, const int pos) {
const BMEdge &edge = *BM_edge_at_index(&const_cast<BMesh &>(bm), i);
loose_edge_data[pos * 2 + 0] = gpu::convert_normal<gpu::PackedNormal>(
bm_vert_no_get(mr, edge.v1));
loose_edge_data[pos * 2 + 1] = gpu::convert_normal<gpu::PackedNormal>(
bm_vert_no_get(mr, edge.v2));
},
exec_mode::grain_size(2048));
mr.loose_verts.foreach_index(
[&](const int i, const int pos) {
const BMVert &vert = *BM_vert_at_index(&const_cast<BMesh &>(bm), i);
loose_vert_data[pos] = gpu::convert_normal<gpu::PackedNormal>(bm_vert_no_get(mr, &vert));
},
exec_mode::grain_size(2048));
}
gpu::VertBufPtr extract_vert_normals(const MeshRenderData &mr)
{
static GPUVertFormat format = GPU_vertformat_from_attribute("vnor",
gpu::VertAttrType::SNORM_10_10_10_2);
gpu::VertBufPtr vbo = gpu::VertBufPtr(GPU_vertbuf_create_with_format(format));
GPU_vertbuf_data_alloc(*vbo, mr.corners_num + mr.loose_indices_num);
MutableSpan vbo_data = vbo->data<gpu::PackedNormal>();
if (mr.extract_type == MeshExtractType::Mesh) {
extract_vert_normals_mesh(mr, vbo_data);
}
else {
extract_vert_normals_bm(mr, vbo_data);
}
return vbo;
}
} // namespace blender::draw

Some files were not shown because too many files have changed in this diff Show More