Skip to content
3 changes: 2 additions & 1 deletion codegen/idlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
identify and remove code paths that are no longer used.
"""

from typing import Dict
from codegen.utils import print
from codegen.files import read_file

Expand Down Expand Up @@ -128,7 +129,7 @@ def peek_line(self):

def parse(self, verbose=True):
self._interfaces = {}
self.classes = {}
self.classes:Dict[str, Interface] = {}
self.structs = {}
self.flags = {}
self.enums = {}
Expand Down
35 changes: 35 additions & 0 deletions codegen/jswriter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Codegen the JS webgpu backend, based on the parsed idl.

write to the backends/js_webgpu/_api.py file.
"""

from codegen.idlparser import get_idl_parser
from textwrap import indent

method_template = """
def {py_method_name}(self, *args, **kwargs):
js_args = to_js(args)
js_kwargs = to_js(kwargs)

js_obj = self._{js_method_name}( *js_args, **js_kwargs )

label = js_kwargs.pop("label", "")
return {class}(js_obj, label=label) # more kwargs? only for get_ and create_?
"""


idl = get_idl_parser()

# todo import our to_js converter functions from elsewhere?

for name, interface in idl.classes.items():
# write idl line, header
# write the to_js block
# get label (where needed?)
# return the constructor call to the base class maybe?
print(name)
for function in interface.functions:
print(" ", function, type(function))
# print(name, interface.functions)

70 changes: 70 additions & 0 deletions examples/browser.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!-- adapted from: https://traineq.org/imgui_bundle_online/projects/min_bundle_pyodide_app/demo_heart.source.txt -->
<!doctype html>
<html>
<head>
wgpu-py on the HTML RenderCanvas canvas with Pyodide:<br>
<script src="https://cdn.jsdelivr.net/pyodide/v0.28.3/full/pyodide.js"></script>
<style>
/* so the demo page can have interactive resizing */
#canvas {
position: absolute;
width: 90%;
height: 90%;
}
</style>
</head>
<body>
<!-- WIP: make it more of a "Playground"? -->
<select id="example-select">
<option value="compute_noop.py">compute_noop.py</option>
<option value="cube.py">cube.py</option>
<option value="triangle.py">triangle.py</option>
<option value="imgui_backend_sea.py">imgui_backend_sea.py</option>
</select>
<!-- maybe something like add example (to test spawning more canvases and also closing them?) -->
<button id="reload-example">Reload example (TODO)</button>
<button id="reload-page" style="background-color: orange;"onclick="location.reload();">Reload page</button>
<br>
<canvas id="canvas" width="640" height="480" style="background-color: lightgrey;"></canvas><br>
pixels got drawn!
<script type="text/javascript">
async function main(){

// fetch the file locally for easier scripting
// --allow-file-access-from-files or local webserver
// TODO: replace the actual code here (unless you have the module)
// pythonCode = await (await fetch("compute_noop.py")).text();
// pythonCode = await (await fetch("compute_matmul.py")).text();
let example_select = document.getElementById("example-select");
let reload_example_button = document.getElementById("reload-example");
pythonCode = await (await fetch(example_select.value)).text();
// pythonCode = await (await fetch("triangle.py")).text();
// pythonCode = await (await fetch("imgui_backend_sea.py")).text();
// pythonCode = await (await fetch("imgui_renderer_sea.py")).text();
// pythonCode = await (await fetch("imgui_basic_example.py")).text();

// Load Pyodide
console.log("Loading pyodide...");
let pyodide = await loadPyodide();
console.log("Pyodide awaiting");
pyodide.setDebug(true);

await pyodide.loadPackage("micropip");
const micropip = pyodide.pyimport("micropip");
await micropip.install('numpy');
await micropip.install('imgui-bundle');
// await micropip.install('rendercanvas');
// await micropip.install('../../../../../../../projects/pygfx-repos/rendercanvas/dist/rendercanvas-2.2.1-py3-none-any.whl'); // local wheel for auto testing
await micropip.install('../../rendercanvas/dist/rendercanvas-2.2.1-py3-none-any.whl'); // local wheel for auto testing
// set WGPU_PY_BUILLD_NOARCH=1
// python -m build -nw
await micropip.install('../dist/wgpu-0.24.0-py3-none-any.whl'); // also local, probably need to name the wheel differently.

// Run the Python code async because some calls are async it seems.
pyodide.runPythonAsync(pythonCode);
// pyodide.runPython(pythonCode);
}
main();
</script>
</body>
</html>
40 changes: 21 additions & 19 deletions examples/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ def setup_drawing_sync(
adapter = wgpu.gpu.request_adapter_sync(power_preference=power_preference)
device = adapter.request_device_sync(required_limits=limits)

pipeline_layout, uniform_buffer, bind_groups = create_pipeline_layout(device)
pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, pipeline_layout)

pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, None)
render_pipeline = device.create_render_pipeline(**pipeline_kwargs)
_, uniform_buffer, bind_groups = create_pipeline_layout(device, render_pipeline)


return get_draw_function(
canvas, device, render_pipeline, uniform_buffer, bind_groups, asynchronous=False
Expand All @@ -55,10 +55,10 @@ async def setup_drawing_async(canvas, limits=None):
adapter = await wgpu.gpu.request_adapter_async(power_preference="high-performance")
device = await adapter.request_device_async(required_limits=limits)

pipeline_layout, uniform_buffer, bind_groups = create_pipeline_layout(device)
pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, pipeline_layout)

pipeline_kwargs = get_render_pipeline_kwargs(canvas, device, None)
render_pipeline = await device.create_render_pipeline_async(**pipeline_kwargs)
_, uniform_buffer, bind_groups = create_pipeline_layout(device, render_pipeline)


return get_draw_function(
canvas, device, render_pipeline, uniform_buffer, bind_groups, asynchronous=True
Expand All @@ -72,14 +72,16 @@ def get_render_pipeline_kwargs(
canvas, device: wgpu.GPUDevice, pipeline_layout: wgpu.GPUPipelineLayout
) -> wgpu.RenderPipelineDescriptor:
context = canvas.get_context("wgpu")
print("context:", context)
render_texture_format = context.get_preferred_format(device.adapter)
print(render_texture_format)
context.configure(device=device, format=render_texture_format)

shader = device.create_shader_module(code=shader_source)

# wgpu.RenderPipelineDescriptor
return wgpu.RenderPipelineDescriptor(
layout=pipeline_layout,
layout="auto",
vertex=wgpu.VertexState(
module=shader,
entry_point="vs_main",
Expand Down Expand Up @@ -120,7 +122,7 @@ def get_render_pipeline_kwargs(
)


def create_pipeline_layout(device: wgpu.GPUDevice):
def create_pipeline_layout(device: wgpu.GPUDevice, pipeline: wgpu.GPURenderPipeline):
# Create uniform buffer - data is uploaded each frame
uniform_buffer = device.create_buffer(
size=uniform_data.nbytes,
Expand Down Expand Up @@ -178,7 +180,7 @@ def create_pipeline_layout(device: wgpu.GPUDevice):
wgpu.BindGroupLayoutEntry(
binding=0,
visibility=wgpu.ShaderStage.VERTEX | wgpu.ShaderStage.FRAGMENT,
buffer={},
buffer=wgpu.BufferBindingLayout(),
)
)

Expand All @@ -192,7 +194,7 @@ def create_pipeline_layout(device: wgpu.GPUDevice):
wgpu.BindGroupLayoutEntry(
binding=1,
visibility=wgpu.ShaderStage.FRAGMENT,
texture={},
texture=wgpu.TextureBindingLayout(),
)
)

Expand All @@ -204,7 +206,7 @@ def create_pipeline_layout(device: wgpu.GPUDevice):
)
bind_groups_layout_entries[0].append(
wgpu.BindGroupLayoutEntry(
binding=2, visibility=wgpu.ShaderStage.FRAGMENT, sampler={}
binding=2, visibility=wgpu.ShaderStage.FRAGMENT, sampler=wgpu.SamplerBindingLayout()
)
)

Expand All @@ -215,17 +217,17 @@ def create_pipeline_layout(device: wgpu.GPUDevice):
for entries, layout_entries in zip(
bind_groups_entries, bind_groups_layout_entries, strict=False
):
bind_group_layout = device.create_bind_group_layout(entries=layout_entries)
bind_group_layouts.append(bind_group_layout)
# bind_group_layout = device.create_bind_group_layout(entries=layout_entries)
# bind_group_layouts.append(bind_group_layout)
bind_groups.append(
device.create_bind_group(layout=bind_group_layout, entries=entries)
device.create_bind_group(layout=pipeline.get_bind_group_layout(0), entries=entries)
)

pipeline_layout = device.create_pipeline_layout(
bind_group_layouts=bind_group_layouts
)
# pipeline_layout = device.create_pipeline_layout(
# bind_group_layouts=bind_group_layouts
# )

return pipeline_layout, uniform_buffer, bind_groups
return None, uniform_buffer, bind_groups


def get_draw_function(
Expand Down Expand Up @@ -463,7 +465,7 @@ async def draw_frame_async():
)
texture_data = np.repeat(texture_data, 64, 0)
texture_data = np.repeat(texture_data, 64, 1)
texture_size = texture_data.shape[1], texture_data.shape[0], 1
texture_size = (texture_data.shape[1], texture_data.shape[0], 1)

# Use numpy to create a struct for the uniform
uniform_dtype = [("transform", "float32", (4, 4))]
Expand Down
9 changes: 5 additions & 4 deletions examples/triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,20 @@ def get_render_pipeline_kwargs(
render_texture_format = context.get_preferred_format(device.adapter)
context.configure(device=device, format=render_texture_format)

shader = device.create_shader_module(code=shader_source)
vert_shader = device.create_shader_module(code=shader_source)
frag_shader = device.create_shader_module(code=shader_source)
pipeline_layout = device.create_pipeline_layout(bind_group_layouts=[])

return wgpu.RenderPipelineDescriptor(
layout=pipeline_layout,
vertex=wgpu.VertexState(
module=shader,
module=vert_shader,
entry_point="vs_main",
),
depth_stencil=None,
multisample=None,
fragment=wgpu.FragmentState(
module=shader,
module=frag_shader,
entry_point="fs_main",
targets=[
wgpu.ColorTargetState(
Expand Down Expand Up @@ -175,7 +176,7 @@ async def draw_frame_async():
if __name__ == "__main__":
from rendercanvas.auto import RenderCanvas, loop

canvas = RenderCanvas(size=(640, 480), title="wgpu triangle example")
canvas = RenderCanvas(size=(640, 480), title="wgpu triangle example", update_mode="continuous")
draw_frame = setup_drawing_sync(canvas)
canvas.request_draw(draw_frame)
loop.run()
4 changes: 2 additions & 2 deletions wgpu/_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def configure(
usage = str_flag_to_int(flags.TextureUsage, usage)

color_space # noqa - not really supported, just assume srgb for now
tone_mapping # noqa - not supported yet
tone_mapping = {} if tone_mapping is None else tone_mapping

# Allow more than the IDL modes, see https://github.com/pygfx/wgpu-py/pull/719
extra_alpha_modes = ["auto", "unpremultiplied", "inherit"] # from webgpu.h
Expand Down Expand Up @@ -1885,7 +1885,7 @@ def set_index_buffer(
call to `GPUDevice.create_render_pipeline()`, it must match.
offset (int): The byte offset in the buffer. Default 0.
size (int): The number of bytes to use. If zero, the remaining size
(after offset) of the buffer is used. Default 0.
(after offset) of the buffer is used.
"""
raise NotImplementedError()

Expand Down
Loading