diff options
author | Clément Foucault <fclem> | 2022-09-02 19:30:48 +0300 |
---|---|---|
committer | Clément Foucault <foucault.clem@gmail.com> | 2022-09-02 19:45:14 +0300 |
commit | 65ad36f5fd71904e776f3deaf77e47b3935ae178 (patch) | |
tree | 8d9db20aa88d1de2934e888e1a345db8dfa25140 /source/blender/draw/tests/draw_pass_test.cc | |
parent | fd47fe4006a54559e0bc97b056a3503bd03e31b2 (diff) |
DRWManager: New implementation.
This is a new implementation of the draw manager using modern
rendering practices and GPU driven culling.
This only ports features that are not considered deprecated or to be
removed.
The old DRW API is kept working along side this new one, and does not
interfeer with it. However this needed some more hacking inside the
draw_view_lib.glsl. At least the create info are well separated.
The reviewer might start by looking at `draw_pass_test.cc` to see the
API in usage.
Important files are `draw_pass.hh`, `draw_command.hh`,
`draw_command_shared.hh`.
In a nutshell (for a developper used to old DRW API):
- `DRWShadingGroups` are replaced by `Pass<T>::Sub`.
- Contrary to DRWShadingGroups, all commands recorded inside a pass or
sub-pass (even binds / push_constant / uniforms) will be executed in order.
- All memory is managed per object (except for Sub-Pass which are managed
by their parent pass) and not from draw manager pools. So passes "can"
potentially be recorded once and submitted multiple time (but this is
not really encouraged for now). The only implicit link is between resource
lifetime and `ResourceHandles`
- Sub passes can be any level deep.
- IMPORTANT: All state propagate from sub pass to subpass. There is no
state stack concept anymore. Ensure the correct render state is set before
drawing anything using `Pass::state_set()`.
- The drawcalls now needs a `ResourceHandle` instead of an `Object *`.
This is to remove any implicit dependency between `Pass` and `Manager`.
This was a huge problem in old implementation since the manager did not
know what to pull from the object. Now it is explicitly requested by the
engine.
- The pases need to be submitted to a `draw::Manager` instance which can
be retrieved using `DRW_manager_get()` (for now).
Internally:
- All object data are stored in contiguous storage buffers. Removing a lot
of complexity in the pass submission.
- Draw calls are sorted and visibility tested on GPU. Making more modern
culling and better instancing usage possible in the future.
- Unit Tests have been added for regression testing and avoid most API
breakage.
- `draw::View` now contains culling data for all objects in the scene
allowing caching for multiple views.
- Bounding box and sphere final setup is moved to GPU.
- Some global resources locations have been hardcoded to reduce complexity.
What is missing:
- ~~Workaround for lack of gl_BaseInstanceARB.~~ Done
- ~~Object Uniform Attributes.~~ Done (Not in this patch)
- Workaround for hardware supporting a maximum of 8 SSBO.
Reviewed By: jbakker
Differential Revision: https://developer.blender.org/D15817
Diffstat (limited to 'source/blender/draw/tests/draw_pass_test.cc')
-rw-r--r-- | source/blender/draw/tests/draw_pass_test.cc | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/source/blender/draw/tests/draw_pass_test.cc b/source/blender/draw/tests/draw_pass_test.cc new file mode 100644 index 00000000000..f8a006d096b --- /dev/null +++ b/source/blender/draw/tests/draw_pass_test.cc @@ -0,0 +1,441 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#include "testing/testing.h" + +#include "draw_manager.hh" +#include "draw_pass.hh" +#include "draw_shader.h" +#include "draw_testing.hh" + +#include <bitset> + +namespace blender::draw { + +static void test_draw_pass_all_commands() +{ + Texture tex; + tex.ensure_2d(GPU_RGBA16, int2(1)); + + UniformBuffer<uint4> ubo; + ubo.push_update(); + + StorageBuffer<uint4> ssbo; + ssbo.push_update(); + + float alpha = 0.0f; + int3 dispatch_size(1); + + PassSimple pass = {"test.all_commands"}; + pass.init(); + pass.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_STENCIL); + pass.clear_color_depth_stencil(float4(0.25f, 0.5f, 100.0f, -2000.0f), 0.5f, 0xF0); + pass.state_stencil(0x80, 0x0F, 0x8F); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + pass.bind_texture("image", tex); + pass.bind_texture("image", &tex); + pass.bind_image("missing_image", tex); /* Should not crash. */ + pass.bind_image("missing_image", &tex); /* Should not crash. */ + pass.bind_ubo("missing_ubo", ubo); /* Should not crash. */ + pass.bind_ubo("missing_ubo", &ubo); /* Should not crash. */ + pass.bind_ssbo("missing_ssbo", ssbo); /* Should not crash. */ + pass.bind_ssbo("missing_ssbo", &ssbo); /* Should not crash. */ + pass.push_constant("alpha", alpha); + pass.push_constant("alpha", &alpha); + pass.push_constant("ModelViewProjectionMatrix", float4x4::identity()); + pass.draw_procedural(GPU_PRIM_TRIS, 1, 3); + + /* Should not crash even if shader is not a compute. This is because we only serialize. */ + /* TODO(fclem): Use real compute shader. */ + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + pass.dispatch(dispatch_size); + pass.dispatch(&dispatch_size); + pass.barrier(GPU_BARRIER_SHADER_IMAGE_ACCESS); + + /* Change references. */ + alpha = 1.0f; + dispatch_size = int3(2); + + std::string result = pass.serialize(); + std::stringstream expected; + expected << ".test.all_commands" << std::endl; + expected << " .state_set(6)" << std::endl; + expected << " .clear(color=(0.25, 0.5, 100, -2000), depth=0.5, stencil=0b11110000))" + << std::endl; + expected << " .stencil_set(write_mask=0b10000000, compare_mask=0b00001111, reference=0b10001111" + << std::endl; + expected << " .shader_bind(gpu_shader_3D_image_modulate_alpha)" << std::endl; + expected << " .bind_texture(0)" << std::endl; + expected << " .bind_texture_ref(0)" << std::endl; + expected << " .bind_image(-1)" << std::endl; + expected << " .bind_image_ref(-1)" << std::endl; + expected << " .bind_uniform_buf(-1)" << std::endl; + expected << " .bind_uniform_buf_ref(-1)" << std::endl; + expected << " .bind_storage_buf(-1)" << std::endl; + expected << " .bind_storage_buf_ref(-1)" << std::endl; + expected << " .push_constant(2, data=0)" << std::endl; + expected << " .push_constant(2, data=1)" << std::endl; + expected << " .push_constant(0, data=(" << std::endl; + expected << "( 1.000000, 0.000000, 0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 1.000000, 0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 1.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ")" << std::endl; + expected << " .draw(inst_len=1, vert_len=3, vert_first=0, res_id=0)" << std::endl; + expected << " .shader_bind(gpu_shader_3D_image_modulate_alpha)" << std::endl; + expected << " .dispatch(1, 1, 1)" << std::endl; + expected << " .dispatch_ref(2, 2, 2)" << std::endl; + expected << " .barrier(4)" << std::endl; + + EXPECT_EQ(result, expected.str()); + + DRW_shape_cache_free(); +} +DRAW_TEST(draw_pass_all_commands) + +static void test_draw_pass_sub_ordering() +{ + PassSimple pass = {"test.sub_ordering"}; + pass.init(); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + pass.push_constant("test_pass", 1); + + PassSimple::Sub &sub1 = pass.sub("Sub1"); + sub1.push_constant("test_sub1", 11); + + PassSimple::Sub &sub2 = pass.sub("Sub2"); + sub2.push_constant("test_sub2", 21); + + /* Will execute after both sub. */ + pass.push_constant("test_pass", 2); + + /* Will execute after sub1. */ + sub2.push_constant("test_sub2", 22); + + /* Will execute before sub2. */ + sub1.push_constant("test_sub1", 12); + + /* Will execute before end of pass. */ + sub2.push_constant("test_sub2", 23); + + std::string result = pass.serialize(); + std::stringstream expected; + expected << ".test.sub_ordering" << std::endl; + expected << " .shader_bind(gpu_shader_3D_image_modulate_alpha)" << std::endl; + expected << " .push_constant(-1, data=1)" << std::endl; + expected << " .Sub1" << std::endl; + expected << " .push_constant(-1, data=11)" << std::endl; + expected << " .push_constant(-1, data=12)" << std::endl; + expected << " .Sub2" << std::endl; + expected << " .push_constant(-1, data=21)" << std::endl; + expected << " .push_constant(-1, data=22)" << std::endl; + expected << " .push_constant(-1, data=23)" << std::endl; + expected << " .push_constant(-1, data=2)" << std::endl; + + EXPECT_EQ(result, expected.str()); +} +DRAW_TEST(draw_pass_sub_ordering) + +static void test_draw_pass_simple_draw() +{ + PassSimple pass = {"test.simple_draw"}; + pass.init(); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + /* Each draw procedural type uses a different batch. Groups are drawn in correct order. */ + pass.draw_procedural(GPU_PRIM_TRIS, 1, 10, 1, {1}); + pass.draw_procedural(GPU_PRIM_POINTS, 4, 20, 2, {2}); + pass.draw_procedural(GPU_PRIM_TRIS, 2, 30, 3, {3}); + pass.draw_procedural(GPU_PRIM_POINTS, 5, 40, 4, ResourceHandle(4, true)); + pass.draw_procedural(GPU_PRIM_LINES, 1, 50, 5, {5}); + pass.draw_procedural(GPU_PRIM_POINTS, 6, 60, 6, {5}); + pass.draw_procedural(GPU_PRIM_TRIS, 3, 70, 7, {6}); + + std::string result = pass.serialize(); + std::stringstream expected; + expected << ".test.simple_draw" << std::endl; + expected << " .shader_bind(gpu_shader_3D_image_modulate_alpha)" << std::endl; + expected << " .draw(inst_len=1, vert_len=10, vert_first=1, res_id=1)" << std::endl; + expected << " .draw(inst_len=4, vert_len=20, vert_first=2, res_id=2)" << std::endl; + expected << " .draw(inst_len=2, vert_len=30, vert_first=3, res_id=3)" << std::endl; + expected << " .draw(inst_len=5, vert_len=40, vert_first=4, res_id=4)" << std::endl; + expected << " .draw(inst_len=1, vert_len=50, vert_first=5, res_id=5)" << std::endl; + expected << " .draw(inst_len=6, vert_len=60, vert_first=6, res_id=5)" << std::endl; + expected << " .draw(inst_len=3, vert_len=70, vert_first=7, res_id=6)" << std::endl; + + EXPECT_EQ(result, expected.str()); + + DRW_shape_cache_free(); +} +DRAW_TEST(draw_pass_simple_draw) + +static void test_draw_pass_multi_draw() +{ + PassMain pass = {"test.multi_draw"}; + pass.init(); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + /* Each draw procedural type uses a different batch. Groups are drawn in reverse order. */ + pass.draw_procedural(GPU_PRIM_TRIS, 1, -1, -1, {1}); + pass.draw_procedural(GPU_PRIM_POINTS, 4, -1, -1, {2}); + pass.draw_procedural(GPU_PRIM_TRIS, 2, -1, -1, {3}); + pass.draw_procedural(GPU_PRIM_POINTS, 5, -1, -1, ResourceHandle(4, true)); + pass.draw_procedural(GPU_PRIM_LINES, 1, -1, -1, {5}); + pass.draw_procedural(GPU_PRIM_POINTS, 6, -1, -1, {5}); + pass.draw_procedural(GPU_PRIM_TRIS, 3, -1, -1, {6}); + + std::string result = pass.serialize(); + std::stringstream expected; + expected << ".test.multi_draw" << std::endl; + expected << " .shader_bind(gpu_shader_3D_image_modulate_alpha)" << std::endl; + expected << " .draw_multi(3)" << std::endl; + expected << " .group(id=2, len=1)" << std::endl; + expected << " .proto(instance_len=1, resource_id=5, front_face)" << std::endl; + expected << " .group(id=1, len=15)" << std::endl; + expected << " .proto(instance_len=5, resource_id=4, back_face)" << std::endl; + expected << " .proto(instance_len=6, resource_id=5, front_face)" << std::endl; + expected << " .proto(instance_len=4, resource_id=2, front_face)" << std::endl; + expected << " .group(id=0, len=6)" << std::endl; + expected << " .proto(instance_len=3, resource_id=6, front_face)" << std::endl; + expected << " .proto(instance_len=2, resource_id=3, front_face)" << std::endl; + expected << " .proto(instance_len=1, resource_id=1, front_face)" << std::endl; + + EXPECT_EQ(result, expected.str()); + + DRW_shape_cache_free(); +} +DRAW_TEST(draw_pass_multi_draw) + +static void test_draw_pass_sortable() +{ + PassSortable pass = {"test.sortable"}; + pass.init(); + + pass.sub("Sub3", 3.0f); + pass.sub("Sub2", 2.0f); + pass.sub("Sub5", 4.0f); + pass.sub("Sub4", 3.0f); + pass.sub("Sub1", 1.0f); + + std::string result = pass.serialize(); + std::stringstream expected; + expected << ".test.sortable" << std::endl; + expected << " .Sub1" << std::endl; + expected << " .Sub2" << std::endl; + expected << " .Sub3" << std::endl; + expected << " .Sub4" << std::endl; + expected << " .Sub5" << std::endl; + + EXPECT_EQ(result, expected.str()); + + DRW_shape_cache_free(); +} +DRAW_TEST(draw_pass_sortable) + +static void test_draw_resource_id_gen() +{ + float4x4 win_mat; + orthographic_m4(win_mat.ptr(), -1, 1, -1, 1, -1, 1); + + View view("test_view"); + view.sync(float4x4::identity(), win_mat); + + Manager drw; + + float4x4 obmat_1 = float4x4::identity(); + float4x4 obmat_2 = float4x4::identity(); + obmat_1.apply_scale(-0.5f); + obmat_2.apply_scale(0.5f); + + drw.begin_sync(); + ResourceHandle handle1 = drw.resource_handle(obmat_1); + ResourceHandle handle2 = drw.resource_handle(obmat_1); + ResourceHandle handle3 = drw.resource_handle(obmat_2); + drw.resource_handle(obmat_2, float3(2), float3(1)); + drw.end_sync(); + + StringRefNull expected = "2 1 1 1 1 3 3 1 1 1 1 1 3 2 2 2 2 2 2 1 1 1 "; + + { + /* Computed on CPU. */ + PassSimple pass = {"test.resource_id"}; + pass.init(); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + pass.draw_procedural(GPU_PRIM_TRIS, 1, -1, -1, handle2); + pass.draw_procedural(GPU_PRIM_POINTS, 4, -1, -1, handle1); + pass.draw_procedural(GPU_PRIM_TRIS, 2, -1, -1, handle3); + pass.draw_procedural(GPU_PRIM_POINTS, 5, -1, -1, handle1); + pass.draw_procedural(GPU_PRIM_LINES, 1, -1, -1, handle3); + pass.draw_procedural(GPU_PRIM_POINTS, 6, -1, -1, handle2); + pass.draw_procedural(GPU_PRIM_TRIS, 3, -1, -1, handle1); + + Manager::SubmitDebugOutput debug = drw.submit_debug(pass, view); + + std::stringstream result; + for (auto val : debug.resource_id) { + result << val << " "; + } + + EXPECT_EQ(result.str(), expected); + } + { + /* Same thing with PassMain (computed on GPU) */ + PassSimple pass = {"test.resource_id"}; + pass.init(); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + pass.draw_procedural(GPU_PRIM_TRIS, 1, -1, -1, handle2); + pass.draw_procedural(GPU_PRIM_POINTS, 4, -1, -1, handle1); + pass.draw_procedural(GPU_PRIM_TRIS, 2, -1, -1, handle3); + pass.draw_procedural(GPU_PRIM_POINTS, 5, -1, -1, handle1); + pass.draw_procedural(GPU_PRIM_LINES, 1, -1, -1, handle3); + pass.draw_procedural(GPU_PRIM_POINTS, 6, -1, -1, handle2); + pass.draw_procedural(GPU_PRIM_TRIS, 3, -1, -1, handle1); + + Manager::SubmitDebugOutput debug = drw.submit_debug(pass, view); + + std::stringstream result; + for (auto val : debug.resource_id) { + result << val << " "; + } + + EXPECT_EQ(result.str(), expected); + } + + DRW_shape_cache_free(); + DRW_shaders_free(); +} +DRAW_TEST(draw_resource_id_gen) + +static void test_draw_visibility() +{ + float4x4 win_mat; + orthographic_m4(win_mat.ptr(), -1, 1, -1, 1, -1, 1); + + View view("test_view"); + view.sync(float4x4::identity(), win_mat); + + Manager drw; + + float4x4 obmat_1 = float4x4::identity(); + float4x4 obmat_2 = float4x4::identity(); + obmat_1.apply_scale(-0.5f); + obmat_2.apply_scale(0.5f); + + drw.begin_sync(); /* Default {0} always visible. */ + drw.resource_handle(obmat_1); /* No bounds, always visible. */ + drw.resource_handle(obmat_1, float3(3), float3(1)); /* Out of view. */ + drw.resource_handle(obmat_2, float3(0), float3(1)); /* Inside view. */ + drw.end_sync(); + + PassMain pass = {"test.visibility"}; + pass.init(); + pass.shader_set(GPU_shader_get_builtin_shader(GPU_SHADER_3D_IMAGE_MODULATE_ALPHA)); + pass.draw_procedural(GPU_PRIM_TRIS, 1, -1); + + Manager::SubmitDebugOutput debug = drw.submit_debug(pass, view); + Vector<uint32_t> expected_visibility = {0}; + + std::stringstream result; + for (auto val : debug.visibility) { + result << std::bitset<32>(val); + } + + EXPECT_EQ(result.str(), "11111111111111111111111111111011"); + + DRW_shape_cache_free(); + DRW_shaders_free(); +} +DRAW_TEST(draw_visibility) + +static void test_draw_manager_sync() +{ + float4x4 obmat_1 = float4x4::identity(); + float4x4 obmat_2 = float4x4::identity(); + obmat_1.apply_scale(-0.5f); + obmat_2.apply_scale(0.5f); + + /* TODO find a way to create a minimum object to test resource handle creation on it. */ + Manager drw; + + drw.begin_sync(); + drw.resource_handle(obmat_1); + drw.resource_handle(obmat_2, float3(2), float3(1)); + drw.end_sync(); + + Manager::DataDebugOutput debug = drw.data_debug(); + + std::stringstream result; + for (const auto &val : debug.matrices) { + result << val; + } + for (const auto &val : debug.bounds) { + result << val; + } + for (const auto &val : debug.infos) { + result << val; + } + + std::stringstream expected; + expected << "ObjectMatrices(" << std::endl; + expected << "model=(" << std::endl; + expected << "( 1.000000, 0.000000, 0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 1.000000, 0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 1.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ", " << std::endl; + expected << "model_inverse=(" << std::endl; + expected << "( 1.000000, -0.000000, 0.000000, -0.000000)" << std::endl; + expected << "( -0.000000, 1.000000, -0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, -0.000000, 1.000000, -0.000000)" << std::endl; + expected << "( -0.000000, 0.000000, -0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ")" << std::endl; + expected << "ObjectMatrices(" << std::endl; + expected << "model=(" << std::endl; + expected << "( -0.500000, -0.000000, -0.000000, 0.000000)" << std::endl; + expected << "( -0.000000, -0.500000, -0.000000, 0.000000)" << std::endl; + expected << "( -0.000000, -0.000000, -0.500000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ", " << std::endl; + expected << "model_inverse=(" << std::endl; + expected << "( -2.000000, 0.000000, -0.000000, -0.000000)" << std::endl; + expected << "( 0.000000, -2.000000, 0.000000, 0.000000)" << std::endl; + expected << "( -0.000000, 0.000000, -2.000000, 0.000000)" << std::endl; + expected << "( -0.000000, -0.000000, 0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ")" << std::endl; + expected << "ObjectMatrices(" << std::endl; + expected << "model=(" << std::endl; + expected << "( 0.500000, 0.000000, 0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.500000, 0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 0.500000, 0.000000)" << std::endl; + expected << "( 0.000000, 0.000000, 0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ", " << std::endl; + expected << "model_inverse=(" << std::endl; + expected << "( 2.000000, -0.000000, 0.000000, -0.000000)" << std::endl; + expected << "( -0.000000, 2.000000, -0.000000, 0.000000)" << std::endl; + expected << "( 0.000000, -0.000000, 2.000000, -0.000000)" << std::endl; + expected << "( -0.000000, 0.000000, -0.000000, 1.000000)" << std::endl; + expected << ")" << std::endl; + expected << ")" << std::endl; + expected << "ObjectBounds(skipped)" << std::endl; + expected << "ObjectBounds(skipped)" << std::endl; + expected << "ObjectBounds(" << std::endl; + expected << ".bounding_corners[0](0.5, 0.5, 0.5)" << std::endl; + expected << ".bounding_corners[1](1, 0, 0)" << std::endl; + expected << ".bounding_corners[2](0, 1, 0)" << std::endl; + expected << ".bounding_corners[3](0, 0, 1)" << std::endl; + expected << ".sphere=(pos=(1, 1, 1), rad=0.866025" << std::endl; + expected << ")" << std::endl; + expected << "ObjectInfos(skipped)" << std::endl; + expected << "ObjectInfos(skipped)" << std::endl; + expected << "ObjectInfos(skipped)" << std::endl; + + EXPECT_EQ(result.str(), expected.str()); + + DRW_shaders_free(); +} +DRAW_TEST(draw_manager_sync) + +} // namespace blender::draw
\ No newline at end of file |