DRW: View: Allow for GPU side specification of view bounds

This allows an engine to perform GPU side view specification and let the
draw manager extract the culling informations (bounds).

To this end, the matrices ubo gets exposed to be able to write to it.

`compute_procedural_bounds()` need to be explicitely called before any
main pass using the culling result.
This commit is contained in:
Clément Foucault 2023-01-18 14:24:25 +01:00
parent 493e3230b4
commit efe51f0220
7 changed files with 195 additions and 1 deletions

View File

@ -530,6 +530,7 @@ set(GLSL_SRC
intern/shaders/draw_debug_print_display_frag.glsl
intern/shaders/draw_debug_print_display_vert.glsl
intern/shaders/draw_resource_finalize_comp.glsl
intern/shaders/draw_view_finalize_comp.glsl
intern/shaders/draw_visibility_comp.glsl
intern/draw_command_shared.hh

View File

@ -24,6 +24,7 @@ static struct {
struct GPUShader *debug_print_display_sh;
struct GPUShader *debug_draw_display_sh;
struct GPUShader *draw_visibility_compute_sh;
struct GPUShader *draw_view_finalize_sh;
struct GPUShader *draw_resource_finalize_sh;
struct GPUShader *draw_command_generate_sh;
} e_data = {{nullptr}};
@ -121,6 +122,14 @@ GPUShader *DRW_shader_draw_visibility_compute_get()
return e_data.draw_visibility_compute_sh;
}
GPUShader *DRW_shader_draw_view_finalize_get()
{
if (e_data.draw_view_finalize_sh == nullptr) {
e_data.draw_view_finalize_sh = GPU_shader_create_from_info_name("draw_view_finalize");
}
return e_data.draw_view_finalize_sh;
}
GPUShader *DRW_shader_draw_resource_finalize_get()
{
if (e_data.draw_resource_finalize_sh == nullptr) {
@ -147,6 +156,7 @@ void DRW_shaders_free()
DRW_SHADER_FREE_SAFE(e_data.debug_print_display_sh);
DRW_SHADER_FREE_SAFE(e_data.debug_draw_display_sh);
DRW_SHADER_FREE_SAFE(e_data.draw_visibility_compute_sh);
DRW_SHADER_FREE_SAFE(e_data.draw_view_finalize_sh);
DRW_SHADER_FREE_SAFE(e_data.draw_resource_finalize_sh);
DRW_SHADER_FREE_SAFE(e_data.draw_command_generate_sh);
}

View File

@ -33,6 +33,7 @@ struct GPUShader *DRW_shader_curves_refine_get(CurvesEvalShader type,
struct GPUShader *DRW_shader_debug_print_display_get(void);
struct GPUShader *DRW_shader_debug_draw_display_get(void);
struct GPUShader *DRW_shader_draw_visibility_compute_get(void);
struct GPUShader *DRW_shader_draw_view_finalize_get(void);
struct GPUShader *DRW_shader_draw_resource_finalize_get(void);
struct GPUShader *DRW_shader_draw_command_generate_get(void);

View File

@ -207,6 +207,16 @@ void View::frustum_culling_sphere_calc(int view_id)
}
}
void View::disable(IndexRange range)
{
/* Set bounding sphere to -1.0f radius will bypass the culling test and treat every instance as
* invisible. */
range = IndexRange(view_len_).intersect(range);
for (auto view_id : range) {
reinterpret_cast<BoundSphere *>(&culling_[view_id].bound_sphere)->radius = -1.0f;
}
}
void View::bind()
{
if (dirty_ && !procedural_) {
@ -219,6 +229,20 @@ void View::bind()
GPU_uniformbuf_bind(culling_, DRW_VIEW_CULLING_UBO_SLOT);
}
void View::compute_procedural_bounds()
{
GPU_debug_group_begin("View.compute_procedural_bounds");
GPUShader *shader = DRW_shader_draw_view_finalize_get();
GPU_shader_bind(shader);
GPU_uniformbuf_bind_as_ssbo(culling_, GPU_shader_get_ssbo(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, uint resource_len, bool debug_freeze)
{
if (debug_freeze && frozen_ == false) {

View File

@ -57,7 +57,7 @@ class View {
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);
BLI_assert(view_len <= DRW_VIEW_MAX);
}
/* For compatibility with old system. Will be removed at some point. */
@ -72,6 +72,16 @@ class View {
void sync(const float4x4 &view_mat, const float4x4 &win_mat, int view_id = 0);
/** Disable a range in the multi-view array. Disabled view will not produce any instances. */
void disable(IndexRange range);
/**
* 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_);
@ -132,6 +142,11 @@ class View {
return (view_len_ == 1) ? 0 : divide_ceil_u(view_len_, 32);
}
UniformArrayBuffer<ViewMatrices, DRW_VIEW_MAX> &matrices_ubo_get()
{
return data_;
}
private:
/** Called from draw manager. */
void bind();

View File

@ -0,0 +1,135 @@
/**
* Compute culling data for each views of a given view buffer.
*/
#pragma BLENDER_REQUIRE(common_math_lib.glsl)
void projmat_dimensions(mat4 winmat,
out float r_left,
out float r_right,
out float r_bottom,
out float r_top,
out float r_near,
out float r_far)
{
const bool is_persp = winmat[3][3] == 0.0;
if (is_persp) {
float near = winmat[3][2] / (winmat[2][2] - 1.0);
r_left = near * ((winmat[2][0] - 1.0) / winmat[0][0]);
r_right = near * ((winmat[2][0] + 1.0) / winmat[0][0]);
r_bottom = near * ((winmat[2][1] - 1.0) / winmat[1][1]);
r_top = near * ((winmat[2][1] + 1.0) / winmat[1][1]);
r_near = near;
r_far = winmat[3][2] / (winmat[2][2] + 1.0);
}
else {
r_left = (-winmat[3][0] - 1.0) / winmat[0][0];
r_right = (-winmat[3][0] + 1.0) / winmat[0][0];
r_bottom = (-winmat[3][1] - 1.0) / winmat[1][1];
r_top = (-winmat[3][1] + 1.0) / winmat[1][1];
r_near = (winmat[3][2] + 1.0) / winmat[2][2];
r_far = (winmat[3][2] - 1.0) / winmat[2][2];
}
}
void frustum_boundbox_calc(mat4 winmat, mat4 viewinv, out vec4 corners[8])
{
float left, right, bottom, top, near, far;
bool is_persp = winmat[3][3] == 0.0;
projmat_dimensions(winmat, 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;
/* Transform into world space. */
for (int i = 0; i < 8; i++) {
corners[i].xyz = transform_point(viewinv, corners[i].xyz);
}
}
void planes_from_projmat(mat4 mat,
out vec4 left,
out vec4 right,
out vec4 bottom,
out vec4 top,
out vec4 near,
out vec4 far)
{
/* References:
*
* https://fgiesen.wordpress.com/2012/08/31/frustum-planes-from-the-projection-matrix/
* http://www8.cs.umu.se/kurser/5DV051/HT12/lab/plane_extraction.pdf
*/
mat = transpose(mat);
left = mat[3] + mat[0];
right = mat[3] - mat[0];
bottom = mat[3] + mat[1];
top = mat[3] - mat[1];
near = mat[3] + mat[2];
far = mat[3] - mat[2];
}
void frustum_culling_planes_calc(mat4 winmat, mat4 viewmat, out vec4 planes[6])
{
mat4 persmat = winmat * viewmat;
planes_from_projmat(persmat, planes[0], planes[5], planes[1], planes[3], planes[4], planes[2]);
/* Normalize. */
for (int p = 0; p < 6; p++) {
planes[p] /= length(planes[p].xyz);
}
}
vec4 frustum_culling_sphere_calc(vec4 corners[8])
{
/* Extract Bounding Sphere */
/* TODO(fclem): This is significantly less precise than CPU, but it isn't used in most cases. */
vec4 bsphere;
bsphere.xyz = (corners[0].xyz + corners[6].xyz) * 0.5;
bsphere.w = 0.0;
for (int i = 0; i < 8; i++) {
bsphere.w = max(bsphere.w, distance(bsphere.xyz, corners[i].xyz));
}
return bsphere;
}
void main()
{
drw_view_id = int(gl_LocalInvocationID.x);
/* Invalid views are disabled. */
if (all(equal(drw_view.viewinv[2].xyz, vec3(0.0)))) {
/* Views with negative radius are treated as disabled. */
view_culling_buf[drw_view_id].bound_sphere = vec4(-1.0);
return;
}
frustum_boundbox_calc(drw_view.winmat, drw_view.viewinv, view_culling_buf[drw_view_id].corners);
frustum_culling_planes_calc(
drw_view.winmat, drw_view.viewmat, view_culling_buf[drw_view_id].planes);
view_culling_buf[drw_view_id].bound_sphere = frustum_culling_sphere_calc(
view_culling_buf[drw_view_id].corners);
}

View File

@ -156,6 +156,14 @@ GPU_SHADER_CREATE_INFO(draw_resource_finalize)
.push_constant(Type::INT, "resource_len")
.compute_source("draw_resource_finalize_comp.glsl");
GPU_SHADER_CREATE_INFO(draw_view_finalize)
.do_static_compilation(true)
.local_group_size(64) /* DRW_VIEW_MAX */
.define("DRW_VIEW_LEN", "64")
.storage_buf(0, Qualifier::READ_WRITE, "ViewCullingData", "view_culling_buf[DRW_VIEW_LEN]")
.compute_source("draw_view_finalize_comp.glsl")
.additional_info("draw_view");
GPU_SHADER_CREATE_INFO(draw_visibility_compute)
.do_static_compilation(true)
.local_group_size(DRW_VISIBILITY_GROUP_SIZE)