Skip to content

Commit

Permalink
Cleanup and comment for tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
fyellin committed Sep 17, 2024
1 parent 52176eb commit fb1c71b
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 44 deletions.
124 changes: 84 additions & 40 deletions tests/test_wgpu_vertex_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,29 @@
multi_draw_indirect,
)

REQUIRED_FEATURES = ["multi-draw-indirect", "indirect-first-instance"]

MAX_INFO = 100

if not can_use_wgpu_lib:
pytest.skip("Skipping tests that need the wgpu lib", allow_module_level=True)


def has_required_features():
adapter = wgpu.gpu.request_adapter(power_preference="high-performance")
return set(REQUIRED_FEATURES) <= adapter.features

"""
The fundamental informartion about any of the many draw commands is the
<vertex_instance, instance_index> 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.
if not has_required_features():
pytest.skip("Tests don't have all required features", allow_module_level=True)
(To modify a buffer in the vertex shader requires the feature vertex-writable-storage)
We call various combinations of draw functions and verify that they generate precisely
the pairs (those possibly in a different order) that we expect.
"""
SHADER_SOURCE = (
f"""
const MAX_INFO: u32 = {MAX_INFO}u;
"""
"""
@group(0) @binding(0) var<storage, read_write> data: array<vec2u, MAX_INFO>;
@group(0) @binding(0) var<storage, read_write> data: array<vec2u>;
@group(0) @binding(1) var<storage, read_write> counter: atomic<u32>;
struct VertexOutput {
Expand Down Expand Up @@ -66,9 +67,16 @@ def has_required_features():


class Runner:
REQUIRED_FEATURES = ["multi-draw-indirect", "indirect-first-instance"]

@classmethod
def is_usable(cls):
adapter = wgpu.gpu.request_adapter(power_preference="high-performance")
return set(cls.REQUIRED_FEATURES) <= adapter.features

def __init__(self):
adapter = wgpu.gpu.request_adapter(power_preference="high-performance")
self.device = adapter.request_device(required_features=REQUIRED_FEATURES)
self.device = adapter.request_device(required_features=self.REQUIRED_FEATURES)
self.output_texture = self.device.create_texture(
# Actual size is immaterial. Could just be 1x1
size=[128, 128],
Expand Down Expand Up @@ -98,7 +106,7 @@ def __init__(self):
},
)

self.vertex_call_buffer = self.device.create_buffer(
self.data_buffer = self.device.create_buffer(
size=MAX_INFO * 2 * 4, usage="STORAGE|COPY_SRC"
)
self.counter_buffer = self.device.create_buffer(
Expand All @@ -107,7 +115,7 @@ def __init__(self):
self.bind_group = self.device.create_bind_group(
layout=self.pipeline.get_bind_group_layout(0),
entries=[
{"binding": 0, "resource": {"buffer": self.vertex_call_buffer}},
{"binding": 0, "resource": {"buffer": self.data_buffer}},
{"binding": 1, "resource": {"buffer": self.counter_buffer}},
],
)
Expand All @@ -121,29 +129,47 @@ def __init__(self):
}
],
}
# Args are [vertex_count, instant_count, first_vertex, first_instance]
self.draw_args1 = [2, 3, 100, 10]
self.draw_args2 = [1, 1, 30, 50]
self.expected_result_draw = set(itertools.product((100, 101), (10, 11, 12))) | {
(30, 50)
}
self.draw_data_buffer = self.device.create_buffer_with_data(
data=np.uint32([0, 0, *self.draw_args1, *self.draw_args2]), usage="INDIRECT"
expected_draw_args1 = set(itertools.product((100, 101), (10, 11, 12)))
expected_draw_args2 = {(30, 50)}
self.expected_result_draw = expected_draw_args1 | expected_draw_args2

# Args are [vertex_count, instance_count, index_buffer_offset, vertex_offset, first_instance]
self.draw_indexed_args1 = [4, 2, 1, 100, 1000]
self.draw_indexed_args2 = [1, 1, 7, 200, 2000]
self.expected_result_draw_indexed = set(
itertools.product((103, 105, 107, 111), (1000, 1001))
)
self.expected_result_draw_indexed.add((219, 2000))

indices = (2, 3, 5, 7, 11, 13, 17, 19)
self.draw_indexed_args1 = (4, 2, 1, 100, 1000)
self.draw_indexed_args2 = (1, 1, 7, 200, 2000)
self.expected_result_draw_indexed = set(
expected_draw_indexed_args1 = set(
itertools.product((103, 105, 107, 111), (1000, 1001))
)
self.expected_result_draw_indexed.add((219, 2000))
self.index_buffer = self.device.create_buffer_with_data(
data=(np.uint32((2, 3, 5, 7, 11, 13, 17, 19))), usage="INDEX"
expected_draw_indexed_args2 = {(219, 2000)}
self.expected_result_draw_indexed = (
expected_draw_indexed_args1 | expected_draw_indexed_args2
)

# 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]), 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]),
usage="INDIRECT",
)

# And let's not forget our index buffer.
self.index_buffer = self.device.create_buffer_with_data(
data=(np.uint32(indices)), usage="INDEX"
)

def create_render_bundle_encoder(self, draw_function):
render_bundle_encoder = self.device.create_render_bundle_encoder(
color_formats=[self.output_texture.format]
Expand All @@ -155,7 +181,7 @@ def create_render_bundle_encoder(self, draw_function):
draw_function(render_bundle_encoder)
return render_bundle_encoder.finish()

def run_function(self, expected_result, draw_function):
def run_draw_test(self, expected_result, draw_function):
encoder = self.device.create_command_encoder()
encoder.clear_buffer(self.counter_buffer)
this_pass = encoder.begin_render_pass(**self.render_pass_descriptor)
Expand All @@ -165,22 +191,20 @@ def run_function(self, expected_result, draw_function):
draw_function(this_pass)
this_pass.end()
self.device.queue.submit([encoder.finish()])
counter_buffer_view = self.device.queue.read_buffer(self.counter_buffer)
count = np.frombuffer(counter_buffer_view, dtype=np.uint32)[0]
count = self.device.queue.read_buffer(self.counter_buffer).cast("i")[0]
if count > MAX_INFO:
pytest.fail("Too many data points written to output buffer")
info_view = self.device.queue.read_buffer(
self.vertex_call_buffer, size=count * 2 * 4
)
# Get the result as a series of tuples
info_view = self.device.queue.read_buffer(self.data_buffer, size=count * 2 * 4)
info = np.frombuffer(info_view, dtype=np.uint32).reshape(-1, 2)
info = [tuple(info[i]) for i in range(len(info))]
info_set = set(info)
assert len(info) == len(info_set)
assert info_set == expected_result

def run_functions(self, expected_result, functions):
for function in functions:
self.run_function(expected_result, function)

if not Runner.is_usable():
pytest.skip("Runner don't have all required features", allow_module_level=True)


@pytest.fixture(scope="module")
Expand All @@ -193,30 +217,30 @@ def draw(encoder):
encoder.draw(*runner.draw_args1)
encoder.draw(*runner.draw_args2)

runner.run_function(runner.expected_result_draw, draw)
runner.run_draw_test(runner.expected_result_draw, draw)


def test_draw_indirect(runner):
def draw(encoder):
encoder.draw_indirect(runner.draw_data_buffer, 8)
encoder.draw_indirect(runner.draw_data_buffer, 8 + 16)

runner.run_function(runner.expected_result_draw, draw)
runner.run_draw_test(runner.expected_result_draw, draw)


def test_draw_mixed(runner):
def draw(encoder):
encoder.draw(*runner.draw_args1)
encoder.draw_indirect(runner.draw_data_buffer, 8 + 16)

runner.run_function(runner.expected_result_draw, draw)
runner.run_draw_test(runner.expected_result_draw, draw)


def test_multi_draw_indirect(runner):
def draw(encoder):
multi_draw_indirect(encoder, runner.draw_data_buffer, offset=8, count=2)

runner.run_function(runner.expected_result_draw, draw)
runner.run_draw_test(runner.expected_result_draw, draw)


def test_draw_via_encoder(runner):
Expand All @@ -226,34 +250,54 @@ def draw(encoder):

render_bundle_encoder = runner.create_render_bundle_encoder(draw)
for _ in range(2):
runner.run_function(
# We run this test twice to verify that encoders are reusable.
runner.run_draw_test(
runner.expected_result_draw,
lambda encoder: encoder.execute_bundles([render_bundle_encoder]),
)


def test_draw_via_multiple_encoders(runner):
# Make sure that execute_bundles() works with multiple encoders.
def draw1(encoder):
encoder.draw(*runner.draw_args1)

def draw2(encoder):
encoder.draw_indirect(runner.draw_data_buffer, 8 + 16)

render_bundle_encoder1 = runner.create_render_bundle_encoder(draw1)
render_bundle_encoder2 = runner.create_render_bundle_encoder(draw2)

runner.run_draw_test(
runner.expected_result_draw,
lambda encoder: encoder.execute_bundles(
[render_bundle_encoder1, render_bundle_encoder2]
),
)


def test_draw_indexed(runner):
def draw(encoder):
encoder.draw_indexed(*runner.draw_indexed_args1)
encoder.draw_indexed(*runner.draw_indexed_args2)

runner.run_function(runner.expected_result_draw_indexed, draw)
runner.run_draw_test(runner.expected_result_draw_indexed, draw)


def test_draw_indexed_indirect(runner):
def draw(encoder):
encoder.draw_indexed_indirect(runner.draw_data_buffer_indexed, 8)
encoder.draw_indexed_indirect(runner.draw_data_buffer_indexed, 8 + 20)

runner.run_function(runner.expected_result_draw_indexed, draw)
runner.run_draw_test(runner.expected_result_draw_indexed, draw)


def test_draw_indexed_mixed(runner):
def draw(encoder):
encoder.draw_indexed_indirect(runner.draw_data_buffer_indexed, 8)
encoder.draw_indexed(*runner.draw_indexed_args2)

runner.run_function(runner.expected_result_draw_indexed, draw)
runner.run_draw_test(runner.expected_result_draw_indexed, draw)


def test_multi_draw_indexed_indirect(runner):
Expand All @@ -262,7 +306,7 @@ def draw(encoder):
encoder, runner.draw_data_buffer_indexed, offset=8, count=2
)

runner.run_function(runner.expected_result_draw_indexed, draw)
runner.run_draw_test(runner.expected_result_draw_indexed, draw)


def test_draw_indexed_via_encoder(runner):
Expand All @@ -272,7 +316,7 @@ def draw(encoder):

render_bundle_encoder = runner.create_render_bundle_encoder(draw)
for _ in range(2):
runner.run_function(
runner.run_draw_test(
runner.expected_result_draw_indexed,
lambda encoder: encoder.execute_bundles([render_bundle_encoder]),
)
Expand Down
8 changes: 4 additions & 4 deletions wgpu/resources/codegen_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, 114 methods, 0 properties
* Validated 37 classes, 116 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
Expand All @@ -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 134 C function calls
* Not using 71 C functions
* Validated 78 C structs
* Validated 133 C function calls
* Not using 70 C functions
* Validated 80 C structs

0 comments on commit fb1c71b

Please sign in to comment.