DRW: Add split visibility and command generation function
This adds the possibility to the user code to group visibility and command generation compute dispatch together to reduce the overhead of PSO and pipeline switch. This PR also make passes that have been submitted (i.e.: that generated commands) as read-only. This allows to automatically remove redundant command generation when submitting the same pass with the same view multiple times, or, remove redundant visibility calculation for the same view in multiple passes. This automation is done for the common usage of the API (using `submit`). Custom usage (using `submit_only`) needs to abide by the rule of the API and update visibility and commands when required. Pull Request: https://projects.blender.org/blender/blender/pulls/129170
This commit is contained in:
committed by
Clément Foucault
parent
cc202bbe1c
commit
20d09435ab
@@ -581,7 +581,7 @@ void Film::init_pass(PassSimple &pass, GPUShader *sh)
|
||||
pass.bind_image("color_accum_img", &color_accum_tx_);
|
||||
pass.bind_image("value_accum_img", &value_accum_tx_);
|
||||
pass.bind_image("cryptomatte_img", &cryptomatte_tx_);
|
||||
copy_ps_.bind_resources(inst_.uniform_data);
|
||||
pass.bind_resources(inst_.uniform_data);
|
||||
}
|
||||
|
||||
void Film::end_sync()
|
||||
|
||||
@@ -734,17 +734,17 @@ void DrawCommandBuf::finalize_commands(Vector<Header, 0> &headers,
|
||||
}
|
||||
}
|
||||
|
||||
void DrawCommandBuf::bind(RecordingState &state,
|
||||
Vector<Header, 0> &headers,
|
||||
Vector<Undetermined, 0> &commands,
|
||||
SubPassVector &sub_passes)
|
||||
void DrawCommandBuf::generate_commands(Vector<Header, 0> &headers,
|
||||
Vector<Undetermined, 0> &commands,
|
||||
SubPassVector &sub_passes)
|
||||
{
|
||||
resource_id_count_ = 0;
|
||||
|
||||
finalize_commands(headers, commands, sub_passes, resource_id_count_, resource_id_buf_);
|
||||
|
||||
resource_id_buf_.push_update();
|
||||
}
|
||||
|
||||
void DrawCommandBuf::bind(RecordingState &state)
|
||||
{
|
||||
if (GPU_shader_draw_parameters_support() == false) {
|
||||
state.resource_id_buf = resource_id_buf_;
|
||||
}
|
||||
@@ -753,13 +753,12 @@ void DrawCommandBuf::bind(RecordingState &state,
|
||||
}
|
||||
}
|
||||
|
||||
void DrawMultiBuf::bind(RecordingState &state,
|
||||
Vector<Header, 0> & /*headers*/,
|
||||
Vector<Undetermined, 0> & /*commands*/,
|
||||
VisibilityBuf &visibility_buf,
|
||||
int visibility_word_per_draw,
|
||||
int view_len,
|
||||
bool use_custom_ids)
|
||||
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");
|
||||
|
||||
@@ -826,9 +825,9 @@ void DrawMultiBuf::bind(RecordingState &state,
|
||||
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. */
|
||||
if (GPU_shader_draw_parameters_support() == false) {
|
||||
GPU_memory_barrier(GPU_BARRIER_VERTEX_ATTRIB_ARRAY);
|
||||
state.resource_id_buf = resource_id_buf_;
|
||||
}
|
||||
else {
|
||||
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
@@ -839,6 +838,16 @@ void DrawMultiBuf::bind(RecordingState &state,
|
||||
GPU_debug_group_end();
|
||||
}
|
||||
|
||||
void DrawMultiBuf::bind(RecordingState &state)
|
||||
{
|
||||
if (GPU_shader_draw_parameters_support() == false) {
|
||||
state.resource_id_buf = resource_id_buf_;
|
||||
}
|
||||
else {
|
||||
GPU_storagebuf_bind(resource_id_buf_, DRW_RESOURCE_ID_SLOT);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
}; // namespace blender::draw::command
|
||||
|
||||
@@ -552,10 +552,11 @@ class DrawCommandBuf {
|
||||
handle};
|
||||
}
|
||||
|
||||
void bind(RecordingState &state,
|
||||
Vector<Header, 0> &headers,
|
||||
Vector<Undetermined, 0> &commands,
|
||||
SubPassVector &sub_passes);
|
||||
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,
|
||||
@@ -720,13 +721,14 @@ class DrawMultiBuf {
|
||||
}
|
||||
}
|
||||
|
||||
void bind(RecordingState &state,
|
||||
Vector<Header, 0> &headers,
|
||||
Vector<Undetermined, 0> &commands,
|
||||
VisibilityBuf &visibility_buf,
|
||||
int visibility_word_per_draw,
|
||||
int view_len,
|
||||
bool use_custom_ids);
|
||||
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);
|
||||
};
|
||||
|
||||
/** \} */
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include "BKE_global.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "GPU_compute.hh"
|
||||
|
||||
#include "draw_debug.hh"
|
||||
@@ -15,9 +16,13 @@
|
||||
#include "draw_manager_c.hh"
|
||||
#include "draw_pass.hh"
|
||||
#include "draw_shader.hh"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
std::atomic<uint32_t> Manager::global_sync_counter_ = 1;
|
||||
|
||||
Manager::~Manager()
|
||||
{
|
||||
for (GPUTexture *texture : acquired_textures) {
|
||||
@@ -28,6 +33,9 @@ Manager::~Manager()
|
||||
|
||||
void Manager::begin_sync()
|
||||
{
|
||||
/* 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();
|
||||
@@ -157,16 +165,77 @@ void Manager::resource_bind()
|
||||
GPU_uniformbuf_bind(attributes_buf_legacy, 2);
|
||||
}
|
||||
|
||||
void Manager::submit(PassSimple &pass, View &view)
|
||||
uint64_t Manager::fingerprint_get()
|
||||
{
|
||||
/* Covers new sync cycle, added resources and different #Manager. */
|
||||
return sync_counter_ | (uint64_t(resource_len_) << 32);
|
||||
}
|
||||
|
||||
void Manager::compute_visibility(View &view)
|
||||
{
|
||||
bool freeze_culling = (U.experimental.use_viewport_debug && DST.draw_ctx.v3d &&
|
||||
(DST.draw_ctx.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::generate_commands(PassMain &pass, View &view)
|
||||
{
|
||||
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(PassSimple &pass)
|
||||
{
|
||||
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::submit_only(PassMain &pass, View &view)
|
||||
{
|
||||
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();
|
||||
|
||||
pass.draw_commands_buf_.bind(state, pass.headers_, pass.commands_, pass.sub_passes_);
|
||||
view.bind();
|
||||
pass.draw_commands_buf_.bind(state);
|
||||
|
||||
resource_bind();
|
||||
|
||||
@@ -177,32 +246,17 @@ void Manager::submit(PassSimple &pass, View &view)
|
||||
|
||||
void Manager::submit(PassMain &pass, View &view)
|
||||
{
|
||||
view.bind();
|
||||
if (view.manager_fingerprint_ != this->fingerprint_get()) {
|
||||
compute_visibility(view);
|
||||
}
|
||||
|
||||
debug_bind();
|
||||
if (pass.manager_fingerprint_ != this->fingerprint_get() ||
|
||||
pass.view_fingerprint_ != view.fingerprint_get())
|
||||
{
|
||||
generate_commands(pass, view);
|
||||
}
|
||||
|
||||
bool freeze_culling = (U.experimental.use_viewport_debug && DST.draw_ctx.v3d &&
|
||||
(DST.draw_ctx.v3d->debug_flag & V3D_DEBUG_FREEZE_CULLING) != 0);
|
||||
|
||||
view.compute_visibility(
|
||||
bounds_buf.current(), infos_buf.current(), resource_len_, freeze_culling);
|
||||
|
||||
command::RecordingState state;
|
||||
state.inverted_view = view.is_inverted();
|
||||
|
||||
pass.draw_commands_buf_.bind(state,
|
||||
pass.headers_,
|
||||
pass.commands_,
|
||||
view.get_visibility_buffer(),
|
||||
view.visibility_word_per_draw(),
|
||||
view.view_len_,
|
||||
pass.use_custom_ids);
|
||||
|
||||
resource_bind();
|
||||
|
||||
pass.submit(state);
|
||||
|
||||
state.cleanup();
|
||||
this->submit_only(pass, view);
|
||||
}
|
||||
|
||||
void Manager::submit(PassSortable &pass, View &view)
|
||||
@@ -212,13 +266,18 @@ void Manager::submit(PassSortable &pass, View &view)
|
||||
this->submit(static_cast<PassMain &>(pass), view);
|
||||
}
|
||||
|
||||
void Manager::submit(PassSimple &pass)
|
||||
void Manager::submit(PassSimple &pass, bool inverted_view)
|
||||
{
|
||||
if (!pass.has_generated_commands()) {
|
||||
generate_commands(pass);
|
||||
}
|
||||
|
||||
debug_bind();
|
||||
|
||||
command::RecordingState state;
|
||||
state.inverted_view = inverted_view;
|
||||
|
||||
pass.draw_commands_buf_.bind(state, pass.headers_, pass.commands_, pass.sub_passes_);
|
||||
pass.draw_commands_buf_.bind(state);
|
||||
|
||||
resource_bind();
|
||||
|
||||
@@ -227,6 +286,15 @@ void Manager::submit(PassSimple &pass)
|
||||
state.cleanup();
|
||||
}
|
||||
|
||||
void Manager::submit(PassSimple &pass, View &view)
|
||||
{
|
||||
debug_bind();
|
||||
|
||||
view.bind();
|
||||
|
||||
this->submit(pass, view.is_inverted());
|
||||
}
|
||||
|
||||
Manager::SubmitDebugOutput Manager::submit_debug(PassSimple &pass, View &view)
|
||||
{
|
||||
submit(pass, view);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "draw_sculpt.hh"
|
||||
#include "draw_view.hh"
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
namespace blender::draw {
|
||||
@@ -110,6 +111,12 @@ class Manager {
|
||||
Vector<GPUTexture *> 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. */
|
||||
uint32_t sync_counter_ = 0;
|
||||
|
||||
/** Number of resource handle recorded. */
|
||||
uint resource_len_ = 0;
|
||||
/** Number of object attribute recorded. */
|
||||
@@ -192,9 +199,73 @@ class Manager {
|
||||
*/
|
||||
void register_layer_attributes(GPUMaterial *material);
|
||||
|
||||
/**
|
||||
* Compute <-> Graphic queue transition is quite slow on some backend. To avoid unecessary
|
||||
* 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);
|
||||
/**
|
||||
* 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 commmand generation.
|
||||
*
|
||||
* IMPORTANT: Generated commands are stored inside #PassMain and overrides commands previously
|
||||
* generated for a previous view.
|
||||
*/
|
||||
void generate_commands(PassMain &pass, View &view);
|
||||
/**
|
||||
* Generate commands on CPU. Doesn't have the GPU compute dispatch overhead.
|
||||
*/
|
||||
void generate_commands(PassSimple &pass);
|
||||
|
||||
/**
|
||||
* Submit a pass for drawing. All resource reference will be dereferenced and commands will be
|
||||
* sent to GPU.
|
||||
* sent to GPU. Visibility and command generation **must** have already been done explicitely
|
||||
* 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);
|
||||
@@ -202,7 +273,7 @@ class Manager {
|
||||
/**
|
||||
* Variant without any view. Must not contain any shader using `draw_view` create info.
|
||||
*/
|
||||
void submit(PassSimple &pass);
|
||||
void submit(PassSimple &pass, bool inverted_view = false);
|
||||
|
||||
/**
|
||||
* Submit a pass for drawing but read back all data buffers for inspection.
|
||||
@@ -242,6 +313,10 @@ class Manager {
|
||||
|
||||
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 ResourceHandle Manager::unique_handle(const ObjectRef &ref)
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
* if any of these reference becomes invalid.
|
||||
*/
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_listbase_wrapper.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
@@ -59,6 +60,7 @@
|
||||
|
||||
#include "intern/gpu_codegen.hh"
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
|
||||
namespace blender::draw {
|
||||
@@ -139,6 +141,9 @@ class PassBase {
|
||||
/** Currently bound shader. Used for interface queries. */
|
||||
GPUShader *shader_;
|
||||
|
||||
uint64_t manager_fingerprint_ = 0;
|
||||
uint64_t view_fingerprint_ = 0;
|
||||
|
||||
public:
|
||||
const char *debug_name;
|
||||
|
||||
@@ -455,6 +460,14 @@ class PassBase {
|
||||
command::Undetermined &create_command(command::Type type);
|
||||
|
||||
void submit(command::RecordingState &state) const;
|
||||
|
||||
bool has_generated_commands() 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 wether or not this pass has generated commands
|
||||
* after sync. Asserts will catch invalid usage . */
|
||||
return manager_fingerprint_ != 0;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename DrawCommandBufType> class Pass : public detail::PassBase<DrawCommandBufType> {
|
||||
@@ -473,6 +486,8 @@ template<typename DrawCommandBufType> class Pass : public detail::PassBase<DrawC
|
||||
|
||||
void init()
|
||||
{
|
||||
this->manager_fingerprint_ = 0;
|
||||
this->view_fingerprint_ = 0;
|
||||
this->headers_.clear();
|
||||
this->commands_.clear();
|
||||
this->sub_passes_.clear();
|
||||
@@ -575,6 +590,9 @@ namespace detail {
|
||||
|
||||
template<class T> inline command::Undetermined &PassBase<T>::create_command(command::Type type)
|
||||
{
|
||||
/* After render commands have been generated, the pass is read only.
|
||||
* Call `init()` to be able modify it again. */
|
||||
BLI_assert_msg(this->has_generated_commands() == false, "Command added after submission");
|
||||
int64_t index = commands_.append_and_get_index({});
|
||||
headers_.append({type, uint(index)});
|
||||
return commands_[index];
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
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;
|
||||
@@ -31,6 +33,9 @@ void View::sync(const float4x4 &view_mat, const float4x4 &win_mat, int 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::sync(const DRWView *view)
|
||||
@@ -232,6 +237,11 @@ void View::bind()
|
||||
|
||||
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");
|
||||
|
||||
GPUShader *shader = DRW_shader_draw_view_finalize_get();
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "DRW_render.hh"
|
||||
|
||||
#include "draw_shader_shared.hh"
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace blender::draw {
|
||||
|
||||
@@ -34,6 +36,12 @@ 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. */
|
||||
|
||||
@@ -44,6 +52,8 @@ class View {
|
||||
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_;
|
||||
|
||||
@@ -180,6 +190,22 @@ class View {
|
||||
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 wether 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 */
|
||||
|
||||
@@ -503,4 +503,133 @@ static void test_draw_manager_sync()
|
||||
}
|
||||
DRAW_TEST(draw_manager_sync)
|
||||
|
||||
static void test_draw_submit_only()
|
||||
{
|
||||
float4x4 projmat = math::projection::orthographic(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
|
||||
float4x4 viewmat = float4x4::identity();
|
||||
|
||||
Manager manager;
|
||||
View view = {"Test"};
|
||||
View view_other = {"Test"};
|
||||
PassSimple pass = {"Test"};
|
||||
PassMain pass_main = {"Test"};
|
||||
PassMain pass_manual = {"Test"};
|
||||
|
||||
manager.begin_sync();
|
||||
manager.end_sync();
|
||||
view.sync(viewmat, projmat);
|
||||
view_other.sync(viewmat, projmat);
|
||||
pass.init();
|
||||
pass_main.init();
|
||||
pass_manual.init();
|
||||
|
||||
/* Auto command and visibility computation. */
|
||||
manager.submit(pass);
|
||||
manager.submit(pass_main, view);
|
||||
|
||||
/* Update manager. */
|
||||
manager.begin_sync();
|
||||
manager.end_sync();
|
||||
|
||||
/* Auto command and visibility computation. */
|
||||
manager.submit(pass);
|
||||
manager.submit(pass_main, view);
|
||||
|
||||
/* Update view. */
|
||||
view.sync(viewmat, projmat);
|
||||
|
||||
/* Auto command and visibility computation. */
|
||||
manager.submit(pass);
|
||||
manager.submit(pass_main, view);
|
||||
|
||||
/* Update both. */
|
||||
manager.begin_sync();
|
||||
manager.end_sync();
|
||||
view.sync(viewmat, projmat);
|
||||
|
||||
/* Auto command and visibility computation. */
|
||||
manager.submit(pass);
|
||||
manager.submit(pass_main, view);
|
||||
|
||||
/* Update both. */
|
||||
manager.begin_sync();
|
||||
manager.end_sync();
|
||||
view.sync(viewmat, projmat);
|
||||
|
||||
{
|
||||
/* Manual command and visibility computation. */
|
||||
manager.compute_visibility(view);
|
||||
manager.generate_commands(pass_manual, view);
|
||||
manager.submit_only(pass_manual, view);
|
||||
|
||||
/* Redundant updates. */
|
||||
EXPECT_BLI_ASSERT(manager.compute_visibility(view),
|
||||
"Resources did not changed, no need to update");
|
||||
EXPECT_BLI_ASSERT(manager.generate_commands(pass_manual, view),
|
||||
"Resources and view did not changed no need to update");
|
||||
}
|
||||
{
|
||||
/* Update view. */
|
||||
view.sync(viewmat, projmat);
|
||||
|
||||
/* Submit before visibility. */
|
||||
EXPECT_BLI_ASSERT(manager.submit_only(pass_manual, view),
|
||||
"compute_visibility was not called on this view");
|
||||
/* Update commands before visibility. */
|
||||
EXPECT_BLI_ASSERT(manager.generate_commands(pass_manual, view),
|
||||
"Resources or view changed, but compute_visibility was not called");
|
||||
|
||||
manager.compute_visibility(view);
|
||||
|
||||
/* Submit before command generation. */
|
||||
EXPECT_BLI_ASSERT(manager.submit_only(pass_manual, view),
|
||||
"View have changed since last generate_commands");
|
||||
|
||||
manager.generate_commands(pass_manual, view);
|
||||
manager.submit_only(pass_manual, view);
|
||||
}
|
||||
{
|
||||
/* Update manager. */
|
||||
manager.begin_sync();
|
||||
manager.end_sync();
|
||||
|
||||
/* Update commands before visibility. */
|
||||
EXPECT_BLI_ASSERT(manager.generate_commands(pass_manual, view),
|
||||
"Resources or view changed, but compute_visibility was not called");
|
||||
/* Submit before visibility. */
|
||||
EXPECT_BLI_ASSERT(manager.submit_only(pass_manual, view),
|
||||
"Resources changed since last compute_visibility");
|
||||
|
||||
manager.compute_visibility(view);
|
||||
|
||||
/* Submit with stale commands. */
|
||||
EXPECT_BLI_ASSERT(manager.submit_only(pass_manual, view),
|
||||
"Resources changed since last generate_command");
|
||||
|
||||
manager.generate_commands(pass_manual, view);
|
||||
manager.submit_only(pass_manual, view);
|
||||
}
|
||||
{
|
||||
pass_manual.init();
|
||||
|
||||
/* Submit before command generation. */
|
||||
EXPECT_BLI_ASSERT(manager.submit_only(pass_manual, view),
|
||||
"generate_command was not called on this pass");
|
||||
manager.generate_commands(pass_manual, view);
|
||||
manager.submit_only(pass_manual, view);
|
||||
}
|
||||
{
|
||||
manager.compute_visibility(view_other);
|
||||
|
||||
/* Submit with a different view before command generation. */
|
||||
EXPECT_BLI_ASSERT(manager.submit_only(pass_manual, view_other),
|
||||
"submitting with a different view");
|
||||
manager.generate_commands(pass_manual, view_other);
|
||||
manager.submit_only(pass_manual, view_other);
|
||||
}
|
||||
|
||||
DRW_shaders_free();
|
||||
}
|
||||
DRAW_TEST(draw_submit_only)
|
||||
|
||||
} // namespace blender::draw
|
||||
|
||||
Reference in New Issue
Block a user