From ee2884147ab2145b4e250ce5397cbfdf4175eabf Mon Sep 17 00:00:00 2001 From: Frank Yellin Date: Fri, 20 Sep 2024 00:34:38 -0700 Subject: [PATCH] multi_draw_indirect_counter --- tests/test_wgpu_vertex_instance.py | 63 ++++++++++++++++++++++++++--- wgpu/backends/wgpu_native/_api.py | 26 ++++++++++++ wgpu/backends/wgpu_native/extras.py | 51 +++++++++++++++++++++-- wgpu/resources/codegen_report.md | 6 +-- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/tests/test_wgpu_vertex_instance.py b/tests/test_wgpu_vertex_instance.py index ecda57dc..12807bcc 100644 --- a/tests/test_wgpu_vertex_instance.py +++ b/tests/test_wgpu_vertex_instance.py @@ -8,16 +8,18 @@ from wgpu.backends.wgpu_native.extras import ( multi_draw_indexed_indirect, multi_draw_indirect, + multi_draw_indirect_count, + multi_draw_indexed_indirect_count, ) -MAX_INFO = 100 +MAX_INFO = 1000 if not can_use_wgpu_lib: pytest.skip("Skipping tests that need the wgpu lib", allow_module_level=True) """ -The fundamental informartion about any of the many draw commands is the +The fundamental information about any of the many draw commands is the pair that is passed to the vertex shader. By using point-list topology, each call to the vertex shader turns into a single call to the fragment shader, where the pair is recorded. @@ -68,7 +70,7 @@ class Runner: REQUIRED_FEATURES = ["indirect-first-instance"] - OPTIONAL_FEATURES = ["multi-draw-indirect"] # we'll be adding more + OPTIONAL_FEATURES = ["multi-draw-indirect", "multi-draw-indirect-count"] @classmethod def is_usable(cls): @@ -82,6 +84,7 @@ def __init__(self): *[x for x in self.OPTIONAL_FEATURES if x in adapter.features], ] self.device = adapter.request_device(required_features=features) + self.output_texture = self.device.create_texture( # Actual size is immaterial. Could just be 1x1 size=[128, 128], @@ -163,11 +166,19 @@ def __init__(self): # We're going to want to try calling these draw functions from a buffer, and it # would be nice to test that these buffers have an offset self.draw_data_buffer = self.device.create_buffer_with_data( - data=np.uint32([0, 0, *self.draw_args1, *self.draw_args2]), + # The zeros at the beginning are to test "offset". + # The zeros at the end are because the _count methods require to buffer to + # be at least byte_offset + 16 * max_count bytes long + data=np.uint32([0, 0, *self.draw_args1, *self.draw_args2, *([0] * 50)]), usage="INDIRECT", ) self.draw_data_buffer_indexed = self.device.create_buffer_with_data( - data=np.uint32([0, 0, *self.draw_indexed_args1, *self.draw_indexed_args2]), + # The zeros at the beginning are to test "offset". + # The zeros at the end are because the _count methods require to buffer to + # be at least byte_offset + 20 * max_count bytes long + data=np.uint32( + [0, 0, *self.draw_indexed_args1, *self.draw_indexed_args2, *([0] * 50)] + ), usage="INDIRECT", ) @@ -211,7 +222,8 @@ def run_draw_test(self, draw_function, indexed, *, expected_result=None): expected_result = self.expected_result_draw_indexed else: expected_result = self.expected_result_draw - assert info_set == expected_result + if info_set != expected_result: + pytest.fail(f"Expected {sorted(info_set)}\nGot {sorted(expected_result)}") if not Runner.is_usable(): @@ -337,5 +349,44 @@ def draw(encoder): ) +@pytest.mark.parametrize("indexed", [False, True]) +@pytest.mark.parametrize("test_max_count", [False, True]) +def test_multi_draw_indirect_count(runner, test_max_count, indexed): + if "multi-draw-indirect-count" not in runner.device.features: + pytest.skip("Must have 'multi-draw-indirect-count' to run") + + print(f"{test_max_count=}, {indexed=} \n") + + count_buffer = runner.device.create_buffer_with_data( + data=(np.int32([10, 2])), usage="INDIRECT" + ) + if indexed: + function = multi_draw_indexed_indirect_count + buffer = runner.draw_data_buffer_indexed + else: + function = multi_draw_indirect_count + buffer = runner.draw_data_buffer + + if test_max_count: + # We pull a count of 10, but we're limiting it to 2 via max_count + count_buffer_offset, max_count = 0, 2 + else: + # We pull a count of 2, and set the max_count to something bigger. Buffer + # is required to be big enough to handle max_count. + count_buffer_offset, max_count = 4, 10 + + def draw(encoder): + function( + encoder, + buffer, + offset=8, + count_buffer=count_buffer, + count_buffer_offset=count_buffer_offset, + max_count=max_count, + ) + + runner.run_draw_test(draw, indexed) + + if __name__ == "__main__": run_tests(globals()) diff --git a/wgpu/backends/wgpu_native/_api.py b/wgpu/backends/wgpu_native/_api.py index 621dd54c..7e337e64 100644 --- a/wgpu/backends/wgpu_native/_api.py +++ b/wgpu/backends/wgpu_native/_api.py @@ -3031,6 +3031,32 @@ def _multi_draw_indexed_indirect(self, buffer, offset, count): self._internal, buffer._internal, int(offset), int(count) ) + def _multi_draw_indirect_count( + self, buffer, offset, count_buffer, count_buffer_offset, max_count + ): + # H: void f(WGPURenderPassEncoder encoder, WGPUBuffer buffer, uint64_t offset, WGPUBuffer count_buffer, uint64_t count_buffer_offset, uint32_t max_count) + libf.wgpuRenderPassEncoderMultiDrawIndirectCount( + self._internal, + buffer._internal, + int(offset), + count_buffer._internal, + int(count_buffer_offset), + int(max_count), + ) + + def _multi_draw_indexed_indirect_count( + self, buffer, offset, count_buffer, count_buffer_offset, max_count + ): + # H: void f(WGPURenderPassEncoder encoder, WGPUBuffer buffer, uint64_t offset, WGPUBuffer count_buffer, uint64_t count_buffer_offset, uint32_t max_count) + libf.wgpuRenderPassEncoderMultiDrawIndexedIndirectCount( + self._internal, + buffer._internal, + int(offset), + count_buffer._internal, + int(count_buffer_offset), + int(max_count), + ) + class GPURenderBundleEncoder( classes.GPURenderBundleEncoder, diff --git a/wgpu/backends/wgpu_native/extras.py b/wgpu/backends/wgpu_native/extras.py index e04196c9..ec9e8c1a 100644 --- a/wgpu/backends/wgpu_native/extras.py +++ b/wgpu/backends/wgpu_native/extras.py @@ -66,22 +66,65 @@ def set_push_constants( def multi_draw_indirect(render_pass_encoder, buffer, *, offset=0, count): """ - This is equvalent to + This is equivalent to for i in range(count): render_pass_encoder.draw(buffer, offset + i * 16) - You must enable the featue "multi-draw-indirect" to use this function. + You must enable the feature "multi-draw-indirect" to use this function. """ render_pass_encoder._multi_draw_indirect(buffer, offset, count) def multi_draw_indexed_indirect(render_pass_encoder, buffer, *, offset=0, count): """ - This is equvalent to + This is equivalent to for i in range(count): render_pass_encoder.draw_indexed(buffer, offset + i * 20) - You must enable the featue "multi-draw-indirect" to use this function. + You must enable the feature "multi-draw-indirect" to use this function. """ render_pass_encoder._multi_draw_indexed_indirect(buffer, offset, count) + + +def multi_draw_indirect_count( + render_pass_encoder, + buffer, + *, + offset=0, + count_buffer, + count_buffer_offset=0, + max_count, +): + """ + This is equivalent to + for i in range(count): + render_pass_encoder.draw(buffer, offset + i * 16) + + You must enable the feature "multi-draw-indirect-count" to use this function. + """ + render_pass_encoder._multi_draw_indirect_count( + buffer, offset, count_buffer, count_buffer_offset, max_count + ) + + +def multi_draw_indexed_indirect_count( + render_pass_encoder, + buffer, + *, + offset=0, + count_buffer, + count_buffer_offset=0, + max_count, +): + """ + This is equivalent to + + for i in range(count): + render_pass_encoder.draw_indexed(buffer, offset + i * 20) + + You must enable the feature "multi-draw-indirect-count" to use this function. + """ + render_pass_encoder._multi_draw_indexed_indirect_count( + buffer, offset, count_buffer, count_buffer_offset, max_count + ) diff --git a/wgpu/resources/codegen_report.md b/wgpu/resources/codegen_report.md index 4f9a16d2..91c19da9 100644 --- a/wgpu/resources/codegen_report.md +++ b/wgpu/resources/codegen_report.md @@ -20,7 +20,7 @@ * Diffs for GPUQueue: add read_buffer, add read_texture, hide copy_external_image_to_texture * Validated 37 classes, 112 methods, 45 properties ### Patching API for backends/wgpu_native/_api.py -* Validated 37 classes, 100 methods, 0 properties +* Validated 37 classes, 102 methods, 0 properties ## Validating backends/wgpu_native/_api.py * Enum field FeatureName.texture-compression-bc-sliced-3d missing in wgpu.h * Enum field FeatureName.clip-distances missing in wgpu.h @@ -35,6 +35,6 @@ * Enum CanvasAlphaMode missing in wgpu.h * Enum CanvasToneMappingMode missing in wgpu.h * Wrote 236 enum mappings and 47 struct-field mappings to wgpu_native/_mappings.py -* Validated 133 C function calls -* Not using 70 C functions +* Validated 135 C function calls +* Not using 68 C functions * Validated 81 C structs