/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2018 Blender Foundation. */ /** \file * \ingroup draw * * \brief Simple API to draw debug shapes in the viewport. */ #include "BKE_object.h" #include "BLI_link_utils.h" #include "GPU_batch.h" #include "GPU_capabilities.h" #include "GPU_debug.h" #include "draw_debug.h" #include "draw_debug.hh" #include "draw_manager.h" #include "draw_shader.h" #include "draw_shader_shared.h" #include #if defined(DEBUG) || defined(WITH_DRAW_DEBUG) # define DRAW_DEBUG #else /* Uncomment to forcibly enable debug draw in release mode. */ //#define DRAW_DEBUG #endif namespace blender::draw { /* -------------------------------------------------------------------- */ /** \name Init and state * \{ */ DebugDraw::DebugDraw() { constexpr int circle_resolution = 16; 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); float point[3] = {cosf(angle), sinf(angle), 0.0f}; sphere_verts_.append( float3(point[(0 + axis) % 3], point[(1 + axis) % 3], point[(2 + axis) % 3])); } } } constexpr int point_resolution = 4; for (auto axis : IndexRange(3)) { for (auto edge : IndexRange(point_resolution)) { for (auto vert : IndexRange(2)) { const float angle = (2 * M_PI) * (edge + vert) / float(point_resolution); float point[3] = {cosf(angle), sinf(angle), 0.0f}; point_verts_.append( float3(point[(0 + axis) % 3], point[(1 + axis) % 3], point[(2 + axis) % 3])); } } } }; void DebugDraw::init() { cpu_print_buf_.command.vertex_len = 0; cpu_print_buf_.command.vertex_first = 0; cpu_print_buf_.command.instance_len = 1; cpu_print_buf_.command.instance_first_array = 0; cpu_draw_buf_.command.vertex_len = 0; cpu_draw_buf_.command.vertex_first = 0; cpu_draw_buf_.command.instance_len = 1; cpu_draw_buf_.command.instance_first_array = 0; gpu_print_buf_.command.vertex_len = 0; gpu_print_buf_.command.vertex_first = 0; gpu_print_buf_.command.instance_len = 1; gpu_print_buf_.command.instance_first_array = 0; gpu_print_buf_used = false; gpu_draw_buf_.command.vertex_len = 0; gpu_draw_buf_.command.vertex_first = 0; gpu_draw_buf_.command.instance_len = 1; gpu_draw_buf_.command.instance_first_array = 0; gpu_draw_buf_used = false; print_col_ = 0; print_row_ = 0; modelmat_reset(); } void DebugDraw::modelmat_reset() { model_mat_ = float4x4::identity(); } void DebugDraw::modelmat_set(const float modelmat[4][4]) { model_mat_ = modelmat; } GPUStorageBuf *DebugDraw::gpu_draw_buf_get() { BLI_assert(GPU_shader_storage_buffer_objects_support()); if (!gpu_draw_buf_used) { gpu_draw_buf_used = true; gpu_draw_buf_.push_update(); } return gpu_draw_buf_; } GPUStorageBuf *DebugDraw::gpu_print_buf_get() { BLI_assert(GPU_shader_storage_buffer_objects_support()); if (!gpu_print_buf_used) { gpu_print_buf_used = true; gpu_print_buf_.push_update(); } return gpu_print_buf_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Draw functions * \{ */ void DebugDraw::draw_line(float3 v1, float3 v2, float4 color) { draw_line(v1, v2, color_pack(color)); } void DebugDraw::draw_polygon(Span poly_verts, float4 color) { BLI_assert(!poly_verts.is_empty()); uint col = color_pack(color); float3 v0 = model_mat_ * poly_verts.last(); for (auto vert : poly_verts) { float3 v1 = model_mat_ * vert; draw_line(v0, v1, col); v0 = v1; } } void DebugDraw::draw_matrix(const float4x4 m4) { float3 v0 = float3(0.0f, 0.0f, 0.0f); float3 v1 = float3(1.0f, 0.0f, 0.0f); float3 v2 = float3(0.0f, 1.0f, 0.0f); float3 v3 = float3(0.0f, 0.0f, 1.0f); mul_project_m4_v3(m4.ptr(), v0); mul_project_m4_v3(m4.ptr(), v1); mul_project_m4_v3(m4.ptr(), v2); mul_project_m4_v3(m4.ptr(), v3); draw_line(v0, v1, float4(1.0f, 0.0f, 0.0f, 1.0f)); draw_line(v0, v2, float4(0.0f, 1.0f, 0.0f, 1.0f)); draw_line(v0, v3, float4(0.0f, 0.0f, 1.0f, 1.0f)); } void DebugDraw::draw_bbox(const BoundBox &bbox, const float4 color) { uint col = color_pack(color); draw_line(bbox.vec[0], bbox.vec[1], col); draw_line(bbox.vec[1], bbox.vec[2], col); draw_line(bbox.vec[2], bbox.vec[3], col); draw_line(bbox.vec[3], bbox.vec[0], col); draw_line(bbox.vec[4], bbox.vec[5], col); draw_line(bbox.vec[5], bbox.vec[6], col); draw_line(bbox.vec[6], bbox.vec[7], col); draw_line(bbox.vec[7], bbox.vec[4], col); draw_line(bbox.vec[0], bbox.vec[4], col); draw_line(bbox.vec[1], bbox.vec[5], col); draw_line(bbox.vec[2], bbox.vec[6], col); draw_line(bbox.vec[3], bbox.vec[7], col); } void DebugDraw::draw_matrix_as_bbox(float4x4 mat, const float4 color) { BoundBox bb; const float min[3] = {-1.0f, -1.0f, -1.0f}, max[3] = {1.0f, 1.0f, 1.0f}; BKE_boundbox_init_from_minmax(&bb, min, max); for (auto i : IndexRange(8)) { mul_project_m4_v3(mat.ptr(), bb.vec[i]); } draw_bbox(bb, color); } void DebugDraw::draw_sphere(const float3 center, float radius, const float4 color) { uint col = 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; draw_line(v0, v1, col); } } void DebugDraw::draw_point(const float3 center, float radius, const float4 color) { uint col = color_pack(color); for (auto i : IndexRange(point_verts_.size() / 2)) { float3 v0 = point_verts_[i * 2] * radius + center; float3 v1 = point_verts_[i * 2 + 1] * radius + center; draw_line(v0, v1, col); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Print functions * \{ */ template<> void DebugDraw::print_value(const uint &value) { print_value_uint(value, false, false, true); } template<> void DebugDraw::print_value(const int &value) { print_value_uint(uint(abs(value)), false, (value < 0), false); } template<> void DebugDraw::print_value(const bool &value) { print_string(value ? "true " : "false"); } template<> void DebugDraw::print_value(const float &val) { std::stringstream ss; ss << std::setw(12) << std::to_string(val); print_string(ss.str()); } template<> void DebugDraw::print_value(const double &val) { print_value(float(val)); } template<> void DebugDraw::print_value_hex(const uint &value) { print_value_uint(value, true, false, false); } template<> void DebugDraw::print_value_hex(const int &value) { print_value_uint(uint(value), true, false, false); } template<> void DebugDraw::print_value_hex(const float &value) { print_value_uint(*reinterpret_cast(&value), true, false, false); } template<> void DebugDraw::print_value_hex(const double &val) { print_value_hex(float(val)); } template<> void DebugDraw::print_value_binary(const uint &value) { print_value_binary(value); } template<> void DebugDraw::print_value_binary(const int &value) { print_value_binary(uint(value)); } template<> void DebugDraw::print_value_binary(const float &value) { print_value_binary(*reinterpret_cast(&value)); } template<> void DebugDraw::print_value_binary(const double &val) { print_value_binary(float(val)); } template<> void DebugDraw::print_value(const float2 &value) { print_no_endl("float2(", value[0], ", ", value[1], ")"); } template<> void DebugDraw::print_value(const float3 &value) { print_no_endl("float3(", value[0], ", ", value[1], ", ", value[1], ")"); } template<> void DebugDraw::print_value(const float4 &value) { print_no_endl("float4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); } template<> void DebugDraw::print_value(const int2 &value) { print_no_endl("int2(", value[0], ", ", value[1], ")"); } template<> void DebugDraw::print_value(const int3 &value) { print_no_endl("int3(", value[0], ", ", value[1], ", ", value[1], ")"); } template<> void DebugDraw::print_value(const int4 &value) { print_no_endl("int4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); } template<> void DebugDraw::print_value(const uint2 &value) { print_no_endl("uint2(", value[0], ", ", value[1], ")"); } template<> void DebugDraw::print_value(const uint3 &value) { print_no_endl("uint3(", value[0], ", ", value[1], ", ", value[1], ")"); } template<> void DebugDraw::print_value(const uint4 &value) { print_no_endl("uint4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Internals * * IMPORTANT: All of these are copied from the shader libraries (`common_debug_draw_lib.glsl` & * `common_debug_print_lib.glsl`). They need to be kept in sync to write the same data. * \{ */ void DebugDraw::draw_line(float3 v1, float3 v2, uint color) { DebugDrawBuf &buf = cpu_draw_buf_; uint index = buf.command.vertex_len; if (index + 2 < DRW_DEBUG_DRAW_VERT_MAX) { buf.verts[index + 0] = vert_pack(model_mat_ * v1, color); buf.verts[index + 1] = vert_pack(model_mat_ * v2, color); buf.command.vertex_len += 2; } } /* Keep in sync with drw_debug_color_pack(). */ uint DebugDraw::color_pack(float4 color) { color = math::clamp(color, 0.0f, 1.0f); uint result = 0; result |= uint(color.x * 255.0f) << 0u; result |= uint(color.y * 255.0f) << 8u; result |= uint(color.z * 255.0f) << 16u; result |= uint(color.w * 255.0f) << 24u; return result; } DRWDebugVert DebugDraw::vert_pack(float3 pos, uint color) { DRWDebugVert vert; vert.pos0 = *reinterpret_cast(&pos.x); vert.pos1 = *reinterpret_cast(&pos.y); vert.pos2 = *reinterpret_cast(&pos.z); vert.color = color; return vert; } void DebugDraw::print_newline() { print_col_ = 0u; print_row_ = ++cpu_print_buf_.command.instance_first_array; } void DebugDraw::print_string_start(uint len) { /* Break before word. */ if (print_col_ + len > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) { print_newline(); } } /* Copied from gpu_shader_dependency. */ void DebugDraw::print_string(std::string str) { size_t len_before_pad = str.length(); /* Pad string to uint size to avoid out of bound reads. */ while (str.length() % 4 != 0) { str += " "; } print_string_start(len_before_pad); for (size_t i = 0; i < len_before_pad; i += 4) { union { uint8_t chars[4]; uint32_t word; }; chars[0] = *(reinterpret_cast(str.c_str()) + i + 0); chars[1] = *(reinterpret_cast(str.c_str()) + i + 1); chars[2] = *(reinterpret_cast(str.c_str()) + i + 2); chars[3] = *(reinterpret_cast(str.c_str()) + i + 3); if (i + 4 > len_before_pad) { chars[len_before_pad - i] = '\0'; } print_char4(word); } } /* Keep in sync with shader. */ void DebugDraw::print_char4(uint data) { /* Convert into char stream. */ for (; data != 0u; data >>= 8u) { uint char1 = data & 0xFFu; /* Check for null terminator. */ if (char1 == 0x00) { break; } /* NOTE: Do not skip the header manually like in GPU. */ uint cursor = cpu_print_buf_.command.vertex_len++; if (cursor < DRW_DEBUG_PRINT_MAX) { /* For future usage. (i.e: Color) */ uint flags = 0u; uint col = print_col_++; uint print_header = (flags << 24u) | (print_row_ << 16u) | (col << 8u); cpu_print_buf_.char_array[cursor] = print_header | char1; /* Break word. */ if (print_col_ > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) { print_newline(); } } } } void DebugDraw::print_append_char(uint char1, uint &char4) { char4 = (char4 << 8u) | char1; } void DebugDraw::print_append_digit(uint digit, uint &char4) { const uint char_A = 0x41u; const uint char_0 = 0x30u; bool is_hexadecimal = digit > 9u; char4 = (char4 << 8u) | (is_hexadecimal ? (char_A + digit - 10u) : (char_0 + digit)); } void DebugDraw::print_append_space(uint &char4) { char4 = (char4 << 8u) | 0x20u; } void DebugDraw::print_value_binary(uint value) { print_string("0b"); print_string_start(10u * 4u); uint digits[10] = {0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}; uint digit = 0u; for (uint i = 0u; i < 32u; i++) { print_append_digit(((value >> i) & 1u), digits[digit / 4u]); digit++; if ((i % 4u) == 3u) { print_append_space(digits[digit / 4u]); digit++; } } /* Numbers are written from right to left. So we need to reverse the order. */ for (int j = 9; j >= 0; j--) { print_char4(digits[j]); } } void DebugDraw::print_value_uint(uint value, const bool hex, bool is_negative, const bool is_unsigned) { print_string_start(3u * 4u); const uint blank_value = hex ? 0x30303030u : 0x20202020u; const uint prefix = hex ? 0x78302020u : 0x20202020u; uint digits[3] = {blank_value, blank_value, prefix}; const uint base = hex ? 16u : 10u; uint digit = 0u; /* Add `u` suffix. */ if (is_unsigned) { print_append_char('u', digits[digit / 4u]); digit++; } /* Number's digits. */ for (; value != 0u || digit == uint(is_unsigned); value /= base) { print_append_digit(value % base, digits[digit / 4u]); digit++; } /* Add negative sign. */ if (is_negative) { print_append_char('-', digits[digit / 4u]); digit++; } /* Need to pad to uint alignment because we are issuing chars in "reverse". */ for (uint i = digit % 4u; i < 4u && i > 0u; i++) { print_append_space(digits[digit / 4u]); digit++; } /* Numbers are written from right to left. So we need to reverse the order. */ for (int j = 2; j >= 0; j--) { print_char4(digits[j]); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Display * \{ */ void DebugDraw::display_lines() { if (cpu_draw_buf_.command.vertex_len == 0 && gpu_draw_buf_used == false) { return; } GPU_debug_group_begin("Lines"); cpu_draw_buf_.push_update(); float4x4 persmat; const DRWView *view = DRW_view_get_active(); DRW_view_persmat_get(view, persmat.ptr(), false); drw_state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS); GPUBatch *batch = drw_cache_procedural_lines_get(); GPUShader *shader = DRW_shader_debug_draw_display_get(); GPU_batch_set_shader(batch, shader); int slot = GPU_shader_get_builtin_ssbo(shader, GPU_STORAGE_BUFFER_DEBUG_VERTS); GPU_shader_uniform_mat4(shader, "persmat", persmat.ptr()); if (gpu_draw_buf_used) { GPU_debug_group_begin("GPU"); GPU_storagebuf_bind(gpu_draw_buf_, slot); GPU_batch_draw_indirect(batch, gpu_draw_buf_, 0); GPU_storagebuf_unbind(gpu_draw_buf_); GPU_debug_group_end(); } GPU_debug_group_begin("CPU"); GPU_storagebuf_bind(cpu_draw_buf_, slot); GPU_batch_draw_indirect(batch, cpu_draw_buf_, 0); GPU_storagebuf_unbind(cpu_draw_buf_); GPU_debug_group_end(); GPU_debug_group_end(); } void DebugDraw::display_prints() { if (cpu_print_buf_.command.vertex_len == 0 && gpu_print_buf_used == false) { return; } GPU_debug_group_begin("Prints"); cpu_print_buf_.push_update(); drw_state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_PROGRAM_POINT_SIZE); GPUBatch *batch = drw_cache_procedural_points_get(); GPUShader *shader = DRW_shader_debug_print_display_get(); GPU_batch_set_shader(batch, shader); int slot = GPU_shader_get_builtin_ssbo(shader, GPU_STORAGE_BUFFER_DEBUG_PRINT); float f_viewport[4]; GPU_viewport_size_get_f(f_viewport); GPU_shader_uniform_2fv(shader, "viewport_size", f_viewport); if (gpu_print_buf_used) { GPU_debug_group_begin("GPU"); GPU_storagebuf_bind(gpu_print_buf_, slot); GPU_batch_draw_indirect(batch, gpu_print_buf_, 0); GPU_storagebuf_unbind(gpu_print_buf_); GPU_debug_group_end(); } GPU_debug_group_begin("CPU"); GPU_storagebuf_bind(cpu_print_buf_, slot); GPU_batch_draw_indirect(batch, cpu_print_buf_, 0); GPU_storagebuf_unbind(cpu_print_buf_); GPU_debug_group_end(); GPU_debug_group_end(); } void DebugDraw::display_to_view() { GPU_debug_group_begin("DebugDraw"); display_lines(); /* Print 3D shapes before text to avoid overlaps. */ display_prints(); /* Init again so we don't draw the same thing twice. */ init(); GPU_debug_group_end(); } } // namespace blender::draw blender::draw::DebugDraw *DRW_debug_get() { if (!GPU_shader_storage_buffer_objects_support()) { return nullptr; } return reinterpret_cast(DST.debug); } /** \} */ /* -------------------------------------------------------------------- */ /** \name C-API private * \{ */ void drw_debug_draw() { #ifdef DRAW_DEBUG if (!GPU_shader_storage_buffer_objects_support() || DST.debug == nullptr) { return; } /* TODO(@fclem): Convenience for now. Will have to move to #DRWManager. */ reinterpret_cast(DST.debug)->display_to_view(); #endif } /** * NOTE: Init is once per draw manager cycle. */ void drw_debug_init() { /* Module should not be used in release builds. */ /* TODO(@fclem): Hide the functions declarations without using `ifdefs` everywhere. */ #ifdef DRAW_DEBUG if (!GPU_shader_storage_buffer_objects_support()) { return; } /* TODO(@fclem): Convenience for now. Will have to move to #DRWManager. */ if (DST.debug == nullptr) { DST.debug = reinterpret_cast(new blender::draw::DebugDraw()); } reinterpret_cast(DST.debug)->init(); #endif } void drw_debug_module_free(DRWDebugModule *module) { if (!GPU_shader_storage_buffer_objects_support()) { return; } if (module != nullptr) { delete reinterpret_cast(module); } } GPUStorageBuf *drw_debug_gpu_draw_buf_get() { return reinterpret_cast(DST.debug)->gpu_draw_buf_get(); } GPUStorageBuf *drw_debug_gpu_print_buf_get() { return reinterpret_cast(DST.debug)->gpu_print_buf_get(); } /** \} */ /* -------------------------------------------------------------------- */ /** \name C-API public * \{ */ void DRW_debug_modelmat_reset() { if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->modelmat_reset(); } void DRW_debug_modelmat(const float modelmat[4][4]) { #ifdef DRAW_DEBUG if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->modelmat_set(modelmat); #else UNUSED_VARS(modelmat); #endif } void DRW_debug_line_v3v3(const float v1[3], const float v2[3], const float color[4]) { if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->draw_line(v1, v2, color); } void DRW_debug_polygon_v3(const float (*v)[3], int vert_len, const float color[4]) { if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->draw_polygon( blender::Span((float3 *)v, vert_len), color); } void DRW_debug_m4(const float m[4][4]) { if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->draw_matrix(m); } void DRW_debug_m4_as_bbox(const float m[4][4], bool invert, const float color[4]) { if (!GPU_shader_storage_buffer_objects_support()) { return; } blender::float4x4 m4 = m; if (invert) { m4 = m4.inverted(); } reinterpret_cast(DST.debug)->draw_matrix_as_bbox(m4, color); } void DRW_debug_bbox(const BoundBox *bbox, const float color[4]) { #ifdef DRAW_DEBUG if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->draw_bbox(*bbox, color); #else UNUSED_VARS(bbox, color); #endif } void DRW_debug_sphere(const float center[3], float radius, const float color[4]) { if (!GPU_shader_storage_buffer_objects_support()) { return; } reinterpret_cast(DST.debug)->draw_sphere(center, radius, color); } /** \} */