Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement push-constants #574

Merged
merged 34 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
efcc55b
Initial implementation of push_constants
fyellin Sep 9, 2024
694410f
Merge branch 'main' into push-constant
fyellin Sep 9, 2024
e0bac8c
Initial implementation of push_constants
fyellin Sep 9, 2024
c131d72
Better handling of limits
fyellin Sep 9, 2024
ca16d0b
One more lint error.
fyellin Sep 9, 2024
5976ccd
And one more typo.
fyellin Sep 9, 2024
b1d07d0
Change limits to use hyphens
fyellin Sep 10, 2024
06adb79
Forgot to uncomment some lines
fyellin Sep 10, 2024
330a6a9
Removed a couple of more comments
fyellin Sep 10, 2024
cdc2d3c
Fix typo in comment.
fyellin Sep 10, 2024
e80d7a4
Move push_constants stuff to extras.py
fyellin Sep 10, 2024
1adb413
Fix flake and codegen
fyellin Sep 10, 2024
d425910
Fix failing test
fyellin Sep 10, 2024
d39ffb7
Linux is failing even though my Mac isn't. I have to figure out what…
fyellin Sep 10, 2024
53bc240
And one last lint problem
fyellin Sep 10, 2024
e57cb9f
First pass at documentation.
fyellin Sep 11, 2024
a87b470
Merge branch 'main' into push-constant
fyellin Sep 11, 2024
01603ba
First pass at documentation.
fyellin Sep 11, 2024
e4975d5
Merge with main
fyellin Sep 11, 2024
f42dc36
Undo accidental modification
fyellin Sep 11, 2024
64dcbeb
See
fyellin Sep 11, 2024
d681be2
Merge branch 'main' into push-constant
fyellin Sep 11, 2024
f31aaae
Found one carryover from move to 22.1 that I forgot to include.
fyellin Sep 12, 2024
6776ae4
Yikes. One more _api change
fyellin Sep 12, 2024
1294efa
Yikes. One more _api change
fyellin Sep 12, 2024
4ed2712
Merge branch 'main' into push-constant
fyellin Sep 12, 2024
3ad2868
Apply suggestions from code review
fyellin Sep 13, 2024
2249a4b
Update comments.
fyellin Sep 13, 2024
7a2b3e8
Merge branch 'main' into push-constant
fyellin Sep 13, 2024
39e52b5
Tiny change to get tests to run again.
fyellin Sep 14, 2024
20d2cec
Merge branch 'main' into push-constant
fyellin Sep 15, 2024
087c4be
Apply suggestions from code review
fyellin Sep 16, 2024
9c728c4
Merge branch 'main' into push-constant
fyellin Sep 16, 2024
6eba317
Merge branch 'main' into push-constant
Korijn Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codegen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def main():

log = io.StringIO()
with PrintToFile(log):
print("# Code generatation report")
print("# Code generation report")
prepare()
update_api()
update_wgpu_native()
Expand Down
2 changes: 1 addition & 1 deletion docs/guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Offscreen
+++++++++

If you render offscreen, or only do compute, you do not need a canvas. You also won't need a GUI toolkit, draw function or enter the event loop.
Instead, you will obtain a command encoder and submit it's records to the queue directly.
Instead, you will obtain a command encoder and submit its records to the queue directly.


Examples and external resources
Expand Down
151 changes: 151 additions & 0 deletions tests/test_set_constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import numpy as np
import pytest

import wgpu.utils
from tests.testutils import can_use_wgpu_lib, run_tests
from wgpu import TextureFormat

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


"""
This code is an amazingly slow way of adding together two 10-element arrays of 32-bit
integers defined by push constants and store them into an output buffer.

The first number of the addition is purposely pulled using the vertex stage, and the
second number from the fragment stage, so that we can ensure that we are correctly
using stage-separated push constants correctly.

The source code assumes the topology is POINT-LIST, so that each call to vertexMain
corresponds with one call to fragmentMain.
"""
COUNT = 10

SHADER_SOURCE = (
f"""
const COUNT = {COUNT}u;
"""
"""
// Put the results here
@group(0) @binding(0) var<storage, read_write> data: array<u32, COUNT>;

struct PushConstants {
values1: array<u32, COUNT>, // VERTEX constants
values2: array<u32, COUNT>, // FRAGMENT constants
}
var<push_constant> push_constants: PushConstants;

struct VertexOutput {
@location(0) index: u32,
@location(1) value: u32,
@builtin(position) position: vec4f,
}

@vertex
fn vertexMain(
@builtin(vertex_index) index: u32,
) -> VertexOutput {
return VertexOutput(index, push_constants.values1[index], vec4f(0, 0, 0, 1));
}

@fragment
fn fragmentMain(@location(0) index: u32,
@location(1) value: u32
) -> @location(0) vec4f {
data[index] = value + push_constants.values2[index];
return vec4f();
}
"""
)

BIND_GROUP_ENTRIES = [
{"binding": 0, "visibility": "FRAGMENT", "buffer": {"type": "storage"}},
]


def run_test(device):
output_texture = device.create_texture(
# Actual size is immaterial. Could just be 1x1
size=[128, 128],
format=TextureFormat.rgba8unorm,
usage="RENDER_ATTACHMENT|COPY_SRC",
)
shader = device.create_shader_module(code=SHADER_SOURCE)
bind_group_layout = device.create_bind_group_layout(entries=BIND_GROUP_ENTRIES)
render_pipeline_layout = device.create_pipeline_layout(
bind_group_layouts=[bind_group_layout],
push_constant_layouts=[
{"visibility": "VERTEX", "start": 0, "end": COUNT * 4},
{"visibility": "FRAGMENT", "start": COUNT * 4, "end": COUNT * 4 * 2},
],
)
pipeline = device.create_render_pipeline(
layout=render_pipeline_layout,
vertex={
"module": shader,
"entry_point": "vertexMain",
},
fragment={
"module": shader,
"entry_point": "fragmentMain",
"targets": [{"format": output_texture.format}],
},
primitive={
"topology": "point-list",
},
)

vertex_call_buffer = device.create_buffer(size=COUNT * 4, usage="STORAGE|COPY_SRC")

bind_group = device.create_bind_group(
layout=pipeline.get_bind_group_layout(0),
entries=[
{"binding": 0, "resource": {"buffer": vertex_call_buffer}},
],
)

render_pass_descriptor = {
"color_attachments": [
{
"clear_value": (0, 0, 0, 0), # only first value matters
"load_op": "clear",
"store_op": "store",
"view": output_texture.create_view(),
}
],
}

buffer1 = np.random.randint(0, 1_000_000, COUNT, dtype=np.uint32)
buffer2 = np.random.randint(0, 1_000_000, COUNT, dtype=np.uint32)

encoder = device.create_command_encoder()
this_pass = encoder.begin_render_pass(**render_pass_descriptor)
this_pass.set_pipeline(pipeline)
this_pass.set_bind_group(0, bind_group)
this_pass.set_push_constants("VERTEX", 0, COUNT * 4, buffer1)
this_pass.set_push_constants("FRAGMENT", COUNT * 4, COUNT * 4, buffer2)
this_pass.draw(COUNT)
this_pass.end()
device.queue.submit([encoder.finish()])
info_view = device.queue.read_buffer(vertex_call_buffer)
info = np.frombuffer(info_view, dtype=np.uint32)
assert all(buffer1 + buffer2 == info)


def get_device():
adapter = wgpu.gpu.request_adapter(power_preference="high-performance")
device = adapter.request_device(
required_features=["push-constants"],
required_limits={"max-push-constant-size": 128},
)
return device


def test_push_constants():
device = get_device()
run_test(device)


if __name__ == "__main__":
run_tests(globals())
22 changes: 21 additions & 1 deletion wgpu/_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,8 +712,13 @@ def create_bind_group(
raise NotImplementedError()

# IDL: GPUPipelineLayout createPipelineLayout(GPUPipelineLayoutDescriptor descriptor);
@apidiff.change("Need push_constants_layouts")
def create_pipeline_layout(
self, *, label="", bind_group_layouts: "List[GPUBindGroupLayout]"
self,
*,
label="",
bind_group_layouts: "List[GPUBindGroupLayout]",
push_constant_layouts: "List[Dict]" = [],
):
"""Create a `GPUPipelineLayout` object, which can be
used in `create_render_pipeline()` or `create_compute_pipeline()`.
Expand Down Expand Up @@ -1867,6 +1872,21 @@ def end_occlusion_query(self):
"""Ends an occlusion query."""
raise NotImplementedError()

@apidiff.add("Part of Push Constants")
def set_push_constants(self, visibility, offset, size_in_bytes, data):
"""
Set push-constant data for subsequent draw calls.

Writes the first size_in_bytes bytes of data to push-constant storage,
starting at the specified offset. These bytes are visible to the pipeline
stages indicated by the visibility argument.
"""
raise NotImplementedError()

"""
void wgpuRenderPassEncoderSetPushConstants(WGPURenderPassEncoder encoder, WGPUShaderStageFlags stages, uint32_t offset, uint32_t sizeBytes, void const * data);
"""


class GPURenderBundle(GPUObjectBase):
"""A reusable bundle of render commands."""
Expand Down
Loading