Files
test2/source/blender/draw/engines/overlay/overlay_next_empty.hh
Clément Foucault 4ba3b1985c Overlay: Avoid engine recreation for clipping region toggle
This changes the shader module reference to a pointer and
set it during `init()`
2025-03-13 22:06:56 +01:00

397 lines
14 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup overlay
*/
#pragma once
#include "overlay_next_base.hh"
#include "overlay_next_image.hh"
namespace blender::draw::overlay {
/**
* Empty object type drawing, including image empties.
*/
class Empties : Overlay {
friend class Cameras;
using EmptyInstanceBuf = ShapeInstanceBuf<ExtraInstanceData>;
private:
/* Images added by Image > Background. Both added in preset view (like Top, Front, ..) and in
* custom view. Object property "In Front" unchecked. */
PassSortable images_back_ps_ = {"images_back_ps_"};
/* All Empty images from cases of `images_ps_`, `images_blend_ps_`, `images_back_ps_`
* with object property "In Front" checked. */
PassSortable images_front_ps_ = {"images_front_ps_"};
/* Images added by Empty > Image and Image > Reference with unchecked image "Opacity".
* Object property "In Front" unchecked. */
PassMain images_ps_ = {"images_ps_"};
/* Images added by Empty > Image and Image > Reference with image "Opacity" checked.
* Object property "In Front" unchecked. */
PassSortable images_blend_ps_ = {"images_blend_ps_"};
PassSimple ps_ = {"Empties"};
struct CallBuffers {
const SelectionType selection_type_;
EmptyInstanceBuf plain_axes_buf = {selection_type_, "plain_axes_buf"};
EmptyInstanceBuf single_arrow_buf = {selection_type_, "single_arrow_buf"};
EmptyInstanceBuf cube_buf = {selection_type_, "cube_buf"};
EmptyInstanceBuf circle_buf = {selection_type_, "circle_buf"};
EmptyInstanceBuf sphere_buf = {selection_type_, "sphere_buf"};
EmptyInstanceBuf cone_buf = {selection_type_, "cone_buf"};
EmptyInstanceBuf arrows_buf = {selection_type_, "arrows_buf"};
EmptyInstanceBuf image_buf = {selection_type_, "image_buf"};
} call_buffers_;
View::OffsetData offset_data_;
float4x4 depth_bias_winmat_;
public:
Empties(const SelectionType selection_type) : call_buffers_{selection_type} {};
void begin_sync(Resources &res, const State &state) final
{
enabled_ = state.is_space_v3d() && state.show_extras();
if (!enabled_) {
return;
}
offset_data_ = state.offset_data_get();
auto init_pass = [&](PassMain &pass, DRWState draw_state) {
pass.init();
pass.state_set(draw_state, state.clipping_plane_count);
pass.shader_set(res.shaders->image_plane_depth_bias.get());
pass.push_constant("depth_bias_winmat", &depth_bias_winmat_);
pass.bind_ubo(OVERLAY_GLOBALS_SLOT, &res.globals_buf);
pass.bind_ubo(DRW_CLIPPING_UBO_SLOT, &res.clip_planes_buf);
res.select_bind(pass);
};
auto init_sortable = [&](PassSortable &pass, DRWState draw_state) {
pass.init();
PassMain::Sub &sub = pass.sub("ResourceBind", -FLT_MAX);
sub.state_set(draw_state, state.clipping_plane_count);
res.select_bind(pass, sub);
};
DRWState draw_state;
draw_state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS;
init_pass(images_ps_, draw_state);
draw_state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_BLEND_ALPHA_PREMUL;
init_sortable(images_back_ps_, draw_state);
init_sortable(images_blend_ps_, draw_state);
draw_state = DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ALPHA_PREMUL;
init_sortable(images_front_ps_, draw_state);
begin_sync(call_buffers_);
}
static void begin_sync(CallBuffers &call_buffers)
{
call_buffers.plain_axes_buf.clear();
call_buffers.single_arrow_buf.clear();
call_buffers.cube_buf.clear();
call_buffers.circle_buf.clear();
call_buffers.sphere_buf.clear();
call_buffers.cone_buf.clear();
call_buffers.arrows_buf.clear();
call_buffers.image_buf.clear();
}
void object_sync(Manager &manager,
const ObjectRef &ob_ref,
Resources &res,
const State &state) final
{
if (!enabled_) {
return;
}
const float4 color = res.object_wire_color(ob_ref, state);
const select::ID select_id = res.select_id(ob_ref);
if (ob_ref.object->empty_drawtype == OB_EMPTY_IMAGE) {
image_sync(ob_ref, select_id, manager, res, state, call_buffers_.image_buf);
return;
}
object_sync(select_id,
ob_ref.object->object_to_world(),
ob_ref.object->empty_drawsize,
ob_ref.object->empty_drawtype,
color,
call_buffers_);
}
static void object_sync(const select::ID select_id,
const float4x4 &matrix,
const float draw_size,
const char empty_drawtype,
const float4 &color,
CallBuffers &call_buffers)
{
ExtraInstanceData data(matrix, color, draw_size);
switch (empty_drawtype) {
case OB_PLAINAXES:
call_buffers.plain_axes_buf.append(data, select_id);
break;
case OB_SINGLE_ARROW:
call_buffers.single_arrow_buf.append(data, select_id);
break;
case OB_CUBE:
call_buffers.cube_buf.append(data, select_id);
break;
case OB_CIRCLE:
call_buffers.circle_buf.append(data, select_id);
break;
case OB_EMPTY_SPHERE:
call_buffers.sphere_buf.append(data, select_id);
break;
case OB_EMPTY_CONE:
call_buffers.cone_buf.append(data, select_id);
break;
case OB_ARROWS:
call_buffers.arrows_buf.append(data, select_id);
break;
}
}
void end_sync(Resources &res, const State &state) final
{
if (!enabled_) {
return;
}
ps_.init();
res.select_bind(ps_);
end_sync(res, state, ps_, call_buffers_);
}
static void end_sync(Resources &res,
const State &state,
PassSimple::Sub &ps,
CallBuffers &call_buffers)
{
ps.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS_EQUAL,
state.clipping_plane_count);
ps.shader_set(res.shaders->extra_shape.get());
ps.bind_ubo(OVERLAY_GLOBALS_SLOT, &res.globals_buf);
ps.bind_ubo(DRW_CLIPPING_UBO_SLOT, &res.clip_planes_buf);
call_buffers.plain_axes_buf.end_sync(ps, res.shapes.plain_axes.get());
call_buffers.single_arrow_buf.end_sync(ps, res.shapes.single_arrow.get());
call_buffers.cube_buf.end_sync(ps, res.shapes.cube.get());
call_buffers.circle_buf.end_sync(ps, res.shapes.circle.get());
call_buffers.sphere_buf.end_sync(ps, res.shapes.empty_sphere.get());
call_buffers.cone_buf.end_sync(ps, res.shapes.empty_cone.get());
call_buffers.arrows_buf.end_sync(ps, res.shapes.arrows.get());
call_buffers.image_buf.end_sync(ps, res.shapes.quad_wire.get());
}
void pre_draw(Manager &manager, View &view) final
{
if (!enabled_) {
return;
}
manager.generate_commands(images_back_ps_, view);
manager.generate_commands(images_ps_, view);
manager.generate_commands(images_blend_ps_, view);
manager.generate_commands(images_front_ps_, view);
depth_bias_winmat_ = offset_data_.winmat_polygon_offset(view.winmat(), -1.0f);
}
void draw_line(Framebuffer &framebuffer, Manager &manager, View &view) final
{
if (!enabled_) {
return;
}
GPU_framebuffer_bind(framebuffer);
manager.submit(ps_, view);
}
void draw_background_images(Framebuffer &framebuffer, Manager &manager, View &view)
{
if (!enabled_) {
return;
}
GPU_framebuffer_bind(framebuffer);
manager.submit_only(images_back_ps_, view);
}
void draw_images(Framebuffer &framebuffer, Manager &manager, View &view)
{
if (!enabled_) {
return;
}
GPU_framebuffer_bind(framebuffer);
manager.submit_only(images_ps_, view);
manager.submit_only(images_blend_ps_, view);
}
void draw_in_front_images(Framebuffer &framebuffer, Manager &manager, View &view)
{
if (!enabled_) {
return;
}
GPU_framebuffer_bind(framebuffer);
manager.submit_only(images_front_ps_, view);
}
private:
void image_sync(const ObjectRef &ob_ref,
select::ID select_id,
Manager &manager,
Resources &res,
const State &state,
EmptyInstanceBuf &empty_image_buf)
{
Object *ob = ob_ref.object;
GPUTexture *tex = nullptr;
::Image *ima = static_cast<::Image *>(ob_ref.object->data);
float4x4 mat;
const bool show_frame = BKE_object_empty_image_frame_is_visible_in_view3d(ob, state.rv3d);
const bool show_image = show_frame &&
BKE_object_empty_image_data_is_visible_in_view3d(ob, state.rv3d);
const bool use_alpha_blend = (ob_ref.object->empty_image_flag &
OB_EMPTY_IMAGE_USE_ALPHA_BLEND) != 0;
const bool use_alpha_premult = ima && (ima->alpha_mode == IMA_ALPHA_PREMUL);
if (!show_frame) {
return;
}
{
/* Calling 'BKE_image_get_size' may free the texture. Get the size from 'tex' instead,
* see: #59347 */
int2 size = int2(0);
if (ima != nullptr) {
ImageUser iuser = *ob->iuser;
Images::stereo_setup(state.scene, state.v3d, ima, &iuser);
tex = BKE_image_get_gpu_texture(ima, &iuser);
if (tex) {
size = int2(GPU_texture_original_width(tex), GPU_texture_original_height(tex));
}
}
CLAMP_MIN(size.x, 1);
CLAMP_MIN(size.y, 1);
float2 image_aspect;
calc_image_aspect(ima, size, image_aspect);
mat = ob->object_to_world();
mat.x_axis() *= image_aspect.x * 0.5f * ob->empty_drawsize;
mat.y_axis() *= image_aspect.y * 0.5f * ob->empty_drawsize;
mat.location() += (mat.x_axis() * (ob->ima_ofs[0] * 2.0f + 1.0f) +
mat.y_axis() * (ob->ima_ofs[1] * 2.0f + 1.0f));
}
if (show_frame) {
const float4 color = res.object_wire_color(ob_ref, state);
empty_image_buf.append(ExtraInstanceData(mat, color, 1.0f), select_id);
}
if (show_image && tex && ((ob->color[3] > 0.0f) || !use_alpha_blend)) {
/* Use the actual depth if we are doing depth tests to determine the distance to the
* object. */
char depth_mode = state.is_depth_only_drawing ? char(OB_EMPTY_IMAGE_DEPTH_DEFAULT) :
ob->empty_image_depth;
PassMain::Sub &pass = create_subpass(state, *ob, use_alpha_blend, mat, res);
pass.bind_texture("imgTexture", tex);
pass.push_constant("imgPremultiplied", use_alpha_premult);
pass.push_constant("imgAlphaBlend", use_alpha_blend);
pass.push_constant("isCameraBackground", false);
pass.push_constant("depthSet", depth_mode != OB_EMPTY_IMAGE_DEPTH_DEFAULT);
pass.push_constant("ucolor", float4(ob->color));
ResourceHandle res_handle = manager.resource_handle(mat);
pass.draw(res.shapes.quad_solid.get(), res_handle, select_id.get());
}
}
PassMain::Sub &create_subpass(const State &state,
const Object &ob,
const bool use_alpha_blend,
const float4x4 &mat,
Resources &res)
{
const bool in_front = state.use_in_front && (ob.dtx & OB_DRAW_IN_FRONT);
if (in_front) {
return create_subpass(state, mat, res, images_front_ps_, true);
}
const char depth_mode = state.is_depth_only_drawing ? char(OB_EMPTY_IMAGE_DEPTH_DEFAULT) :
ob.empty_image_depth;
switch (depth_mode) {
case OB_EMPTY_IMAGE_DEPTH_BACK:
return create_subpass(state, mat, res, images_back_ps_, false);
case OB_EMPTY_IMAGE_DEPTH_FRONT:
return create_subpass(state, mat, res, images_front_ps_, true);
case OB_EMPTY_IMAGE_DEPTH_DEFAULT:
default:
return use_alpha_blend ? create_subpass(state, mat, res, images_blend_ps_, true) :
images_ps_;
}
}
PassMain::Sub &create_subpass(const State &state,
const float4x4 &mat,
Resources &res,
PassSortable &parent,
bool depth_bias)
{
const float3 tmp = state.camera_position - mat.location();
const float z = -math::dot(state.camera_forward, tmp);
PassMain::Sub &sub = parent.sub("Sub", z);
if (depth_bias) {
sub.shader_set(res.shaders->image_plane_depth_bias.get());
sub.push_constant("depth_bias_winmat", &depth_bias_winmat_);
}
else {
sub.shader_set(res.shaders->image_plane.get());
}
sub.bind_ubo(OVERLAY_GLOBALS_SLOT, &res.globals_buf);
sub.bind_ubo(DRW_CLIPPING_UBO_SLOT, &res.clip_planes_buf);
return sub;
};
static void calc_image_aspect(::Image *ima, const int2 &size, float2 &r_image_aspect)
{
/* if no image, make it a 1x1 empty square, honor scale & offset */
const float2 ima_dim = ima ? float2(size.x, size.y) : float2(1.0f);
/* Get the image aspect even if the buffer is invalid */
float2 sca(1.0f);
if (ima) {
if (ima->aspx > ima->aspy) {
sca.y = ima->aspy / ima->aspx;
}
else if (ima->aspx < ima->aspy) {
sca.x = ima->aspx / ima->aspy;
}
}
const float2 scale_inv(ima_dim.x * sca.x, ima_dim.y * sca.y);
r_image_aspect = (scale_inv.x > scale_inv.y) ? float2(1.0f, scale_inv.y / scale_inv.x) :
float2(scale_inv.x / scale_inv.y, 1.0f);
}
};
} // namespace blender::draw::overlay