Skip to content

Commit 0b54483

Browse files
authored
Add support for GLSL (#333)
* Add support for GLSL * Add to the docxs * update codegen * fmt
1 parent 3cd24db commit 0b54483

File tree

5 files changed

+209
-27
lines changed

5 files changed

+209
-27
lines changed

examples/triangle_glsl.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""
2+
The triangle example, using GLSL shaders.
3+
4+
"""
5+
6+
import wgpu
7+
8+
9+
# %% Shaders
10+
11+
12+
vertex_shader = """
13+
#version 450 core
14+
layout(location = 0) out vec4 color;
15+
void main()
16+
{
17+
vec2 positions[3] = vec2[3](
18+
vec2(0.0, -0.5),
19+
vec2(0.5, 0.5),
20+
vec2(-0.5, 0.75)
21+
);
22+
vec3 colors[3] = vec3[3]( // srgb colors
23+
vec3(1.0, 1.0, 0.0),
24+
vec3(1.0, 0.0, 1.0),
25+
vec3(0.0, 1.0, 1.0)
26+
);
27+
int index = int(gl_VertexID);
28+
gl_Position = vec4(positions[index], 0.0, 1.0);
29+
color = vec4(colors[index], 1.0);
30+
}
31+
"""
32+
33+
fragment_shader = """
34+
#version 450 core
35+
out vec4 FragColor;
36+
layout(location = 0) in vec4 color;
37+
void main()
38+
{
39+
vec3 physical_color = pow(color.rgb, vec3(2.2)); // gamma correct
40+
FragColor = vec4(physical_color, color.a);
41+
}
42+
"""
43+
44+
45+
# %% The wgpu calls
46+
47+
48+
def main(canvas, power_preference="high-performance", limits=None):
49+
"""Regular function to setup a viz on the given canvas."""
50+
# Note: passing the canvas here can (oddly enough) prevent the
51+
# adapter from being found. Seen with wx/Linux.
52+
adapter = wgpu.request_adapter(canvas=None, power_preference=power_preference)
53+
device = adapter.request_device(required_limits=limits)
54+
return _main(canvas, device)
55+
56+
57+
async def main_async(canvas):
58+
"""Async function to setup a viz on the given canvas."""
59+
adapter = await wgpu.request_adapter_async(
60+
canvas=canvas, power_preference="high-performance"
61+
)
62+
device = await adapter.request_device_async(required_limits={})
63+
return _main(canvas, device)
64+
65+
66+
def _main(canvas, device):
67+
vert_shader = device.create_shader_module(label="triangle_vert", code=vertex_shader)
68+
frag_shader = device.create_shader_module(
69+
label="triangle_frag", code=fragment_shader
70+
)
71+
72+
# No bind group and layout, we should not create empty ones.
73+
pipeline_layout = device.create_pipeline_layout(bind_group_layouts=[])
74+
75+
present_context = canvas.get_context()
76+
render_texture_format = present_context.get_preferred_format(device.adapter)
77+
present_context.configure(device=device, format=render_texture_format)
78+
79+
render_pipeline = device.create_render_pipeline(
80+
layout=pipeline_layout,
81+
vertex={
82+
"module": vert_shader,
83+
"entry_point": "main",
84+
"buffers": [],
85+
},
86+
primitive={
87+
"topology": wgpu.PrimitiveTopology.triangle_list,
88+
"front_face": wgpu.FrontFace.ccw,
89+
"cull_mode": wgpu.CullMode.none,
90+
},
91+
depth_stencil=None,
92+
multisample=None,
93+
fragment={
94+
"module": frag_shader,
95+
"entry_point": "main",
96+
"targets": [
97+
{
98+
"format": render_texture_format,
99+
"blend": {
100+
"color": (
101+
wgpu.BlendFactor.one,
102+
wgpu.BlendFactor.zero,
103+
wgpu.BlendOperation.add,
104+
),
105+
"alpha": (
106+
wgpu.BlendFactor.one,
107+
wgpu.BlendFactor.zero,
108+
wgpu.BlendOperation.add,
109+
),
110+
},
111+
},
112+
],
113+
},
114+
)
115+
116+
def draw_frame():
117+
current_texture_view = present_context.get_current_texture()
118+
command_encoder = device.create_command_encoder()
119+
120+
render_pass = command_encoder.begin_render_pass(
121+
color_attachments=[
122+
{
123+
"view": current_texture_view,
124+
"resolve_target": None,
125+
"clear_value": (0, 0, 0, 1),
126+
"load_op": wgpu.LoadOp.clear,
127+
"store_op": wgpu.StoreOp.store,
128+
}
129+
],
130+
)
131+
132+
render_pass.set_pipeline(render_pipeline)
133+
# render_pass.set_bind_group(0, no_bind_group, [], 0, 1)
134+
render_pass.draw(3, 1, 0, 0)
135+
render_pass.end()
136+
device.queue.submit([command_encoder.finish()])
137+
138+
canvas.request_draw(draw_frame)
139+
return device
140+
141+
142+
if __name__ == "__main__":
143+
import wgpu.backends.rs # noqa: F401, Select Rust backend
144+
from wgpu.gui.auto import WgpuCanvas, run
145+
146+
canvas = WgpuCanvas(size=(640, 480), title="wgpu triangle")
147+
main(canvas)
148+
run()

tests/test_rs_basics.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,10 @@ def test_shader_module_creation_spirv():
153153

154154
code1 = compute_shader_spirv
155155
assert isinstance(code1, bytes)
156-
code2 = type("CodeObject", (object,), {"to_bytes": lambda: code1})
157-
code3 = type("CodeObject", (object,), {"to_spirv": lambda: code1})
158156
code4 = type("CodeObject", (object,), {})
159157

160158
m1 = device.create_shader_module(code=code1)
161-
m2 = device.create_shader_module(code=code2)
162-
m3 = device.create_shader_module(code=code3)
163-
164-
for m in (m1, m2, m3):
165-
assert m.compilation_info() == []
159+
assert m1.compilation_info() == []
166160

167161
with raises(TypeError):
168162
device.create_shader_module(code=code4)

wgpu/backends/rs.py

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,25 +1053,57 @@ def create_shader_module(
10531053
for val in hints.values():
10541054
check_struct("ShaderModuleCompilationHint", val)
10551055
if isinstance(code, str):
1056-
# WGSL
1057-
# H: chain: WGPUChainedStruct, code: char *
1058-
source_struct = new_struct_p(
1059-
"WGPUShaderModuleWGSLDescriptor *",
1060-
code=ffi.new("char []", code.encode()),
1061-
# not used: chain
1056+
looks_like_wgsl = any(
1057+
x in code for x in ("@compute", "@vertex", "@fragment")
10621058
)
1063-
source_struct[0].chain.next = ffi.NULL
1064-
source_struct[0].chain.sType = lib.WGPUSType_ShaderModuleWGSLDescriptor
1065-
else:
1066-
# Must be Spirv then
1067-
if isinstance(code, bytes):
1068-
data = code
1069-
elif hasattr(code, "to_bytes"):
1070-
data = code.to_bytes()
1071-
elif hasattr(code, "to_spirv"):
1072-
data = code.to_spirv()
1059+
looks_like_glsl = code.lstrip().startswith("#version ")
1060+
if looks_like_glsl and not looks_like_wgsl:
1061+
# === GLSL
1062+
if "comp" in label.lower():
1063+
c_stage = flags.ShaderStage.COMPUTE
1064+
elif "vert" in label.lower():
1065+
c_stage = flags.ShaderStage.VERTEX
1066+
elif "frag" in label.lower():
1067+
c_stage = flags.ShaderStage.FRAGMENT
1068+
else:
1069+
raise ValueError(
1070+
"GLSL shader needs to use the label to specify compute/vertex/fragment stage."
1071+
)
1072+
defines = []
1073+
if c_stage == flags.ShaderStage.VERTEX:
1074+
defines.append(
1075+
# H: name: char *, value: char *
1076+
new_struct(
1077+
"WGPUShaderDefine",
1078+
name=ffi.new("char []", "gl_VertexID".encode()),
1079+
value=ffi.new("char []", "gl_VertexIndex".encode()),
1080+
)
1081+
)
1082+
c_defines = ffi.new("WGPUShaderDefine []", defines)
1083+
# H: chain: WGPUChainedStruct, stage: WGPUShaderStage, code: char *, defineCount: int, defines: WGPUShaderDefine*/WGPUShaderDefine *
1084+
source_struct = new_struct_p(
1085+
"WGPUShaderModuleGLSLDescriptor *",
1086+
code=ffi.new("char []", code.encode()),
1087+
stage=c_stage,
1088+
defineCount=len(defines),
1089+
defines=c_defines,
1090+
# not used: chain
1091+
)
1092+
source_struct[0].chain.next = ffi.NULL
1093+
source_struct[0].chain.sType = lib.WGPUSType_ShaderModuleGLSLDescriptor
10731094
else:
1074-
raise TypeError("Shader code must be str for WGSL, or bytes for SpirV.")
1095+
# === WGSL
1096+
# H: chain: WGPUChainedStruct, code: char *
1097+
source_struct = new_struct_p(
1098+
"WGPUShaderModuleWGSLDescriptor *",
1099+
code=ffi.new("char []", code.encode()),
1100+
# not used: chain
1101+
)
1102+
source_struct[0].chain.next = ffi.NULL
1103+
source_struct[0].chain.sType = lib.WGPUSType_ShaderModuleWGSLDescriptor
1104+
elif isinstance(code, bytes):
1105+
# === Spirv
1106+
data = code
10751107
# Validate
10761108
magic_nr = b"\x03\x02#\x07" # 0x7230203
10771109
if data[:4] != magic_nr:
@@ -1088,6 +1120,10 @@ def create_shader_module(
10881120
)
10891121
source_struct[0].chain.next = ffi.NULL
10901122
source_struct[0].chain.sType = lib.WGPUSType_ShaderModuleSPIRVDescriptor
1123+
else:
1124+
raise TypeError(
1125+
"Shader code must be str for WGSL or GLSL, or bytes for SpirV."
1126+
)
10911127

10921128
# Note, we could give hints here that specify entrypoint and pipelinelayout before compiling
10931129
# H: nextInChain: WGPUChainedStruct *, label: char *, hintCount: int, hints: WGPUShaderModuleCompilationHint *

wgpu/base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,10 +641,14 @@ def create_shader_module(
641641
):
642642
"""Create a :class:`GPUShaderModule` object from shader source.
643643
644+
The primary shader language is WGSL, though SpirV is also supported,
645+
as well as GLSL (experimental).
646+
644647
Arguments:
645648
label (str): A human readable label. Optional.
646-
code (str | bytes): The shader code, as WGSL text or binary SpirV
647-
(or an object implementing ``to_spirv()`` or ``to_bytes()``).
649+
code (str | bytes): The shader code, as WGSL, GLSL or SpirV.
650+
For GLSL code, the label must be given and contain the word
651+
'comp', 'vert' or 'frag'. For SpirV the code must be bytes.
648652
hints: unused.
649653
"""
650654
raise NotImplementedError()

wgpu/resources/codegen_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@
2828
* Wrote 231 enum mappings and 49 struct-field mappings to rs_mappings.py
2929
* Validated 89 C function calls
3030
* Not using 93 C functions
31-
* Validated 70 C structs
31+
* Validated 72 C structs

0 commit comments

Comments
 (0)