shaderc-rs-py
provides Python bindings for the shaderc
library through its Rust bindings for compiling GLSL shaders to SPIR-V binary or assembly directly from Python code. By leveraging PyO3
, shaderc-rs-py
aims to provide a flexible API surface, including support for advanced shader compilation options, preprocessing capabilities, macro definitions, and SPIR-V assembly generation.
Warning
|
This library is currently experimental. While it offers a broad range of functionality, it may contain memory leaks or other issues. It has not been thoroughly tested, and it is recommended that you use it cautiously in production contexts. |
-
Provides Python bindings over shaderc-rs (Rust) for GLSL-to-SPIR-V compilation.
-
Offers fine-grained control of compilation options (optimization levels, target environment, debug info, macro definitions, and more).
-
Generates both SPIR-V binary and assembly.
-
Includes examples demonstrating real-time shader editing with wgpu-py and PySide6.
-
Can be built into Python wheels for multiple platforms (Windows, macOS, and Linux) using Maturin and PyO3.
The recommended Python version is 3.12 or higher.
You can clone the repository and build from source, or install from the PyPI package release (pip install shaderc-rs-py
or uv add shaderc-rs-py
).
If you plan to work on this project locally, follow these steps:
-
Install UV (a CLI tool for Python environment management).
curl -LsSf https://astral.sh/uv/install.sh | sh # or on Windows PowerShell: # powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
-
Update UV and synchronize the workspace:
uv self update uv sync
-
Activate the virtual environment and install any development dependencies. For instance:
source .venv/bin/activate # or on Windows PowerShell: # .venv\Scripts\activate uv add ruff maturin develop --uv
Alternatively, to set up the project directly:
git clone https://github.com/mehmetoguzderin/shaderc-rs-py cd shaderc-rs-py uv sync code .
import shadercrs compiler = shadercrs.Compiler() options = shadercrs.CompileOptions() vertex_shader = """ #version 450 layout(location = 0) in vec3 position; layout(location = 1) in vec2 texcoord; layout(location = 0) out vec2 fragTexcoord; void main() { gl_Position = vec4(position, 1.0); fragTexcoord = texcoord; } """ result = compiler.compile_into_spirv( source=vertex_shader, stage="vertex", input_file_name="shader.vert", entry_point="main", ) spirv_binary = result.as_binary() print(f"Compiled SPIR-V size: {len(spirv_binary)} words") if result.get_num_warnings() > 0: print("Compilation warnings:", result.get_warning_messages())
import shadercrs compiler = shadercrs.Compiler() options = shadercrs.CompileOptions() options.set_source_language("glsl") options.set_target_env("vulkan", "vulkan_1_1") options.set_optimization_level("performance") options.set_generate_debug_info() options.add_macro_definition("MAX_VERTICES", "1024") fragment_shader = """ #version 450 #define MAX_LIGHTS 4 layout(location = 0) in vec2 fragTexcoord; layout(location = 0) out vec4 outColor; void main() { outColor = vec4(fragTexcoord, 0.0, 1.0); #ifdef MAX_VERTICES #endif } """ result = compiler.compile_into_spirv( source=fragment_shader, stage="fragment", input_file_name="shader.frag", entry_point="main", options=options, ) spirv_asm = compiler.compile_into_spirv_assembly( source=fragment_shader, stage="fragment", input_file_name="shader.frag", entry_point="main", options=options, ) print("SPIR-V Assembly:", spirv_asm.as_text())
import shadercrs def compile_shader(source, shader_value): stage = "vertex" compiler = shadercrs.Compiler() options = shadercrs.CompileOptions() options.add_macro_definition("SHADER_VALUE", shader_value) options.add_macro_definition("DEBUG", "1") try: preprocessed = compiler.preprocess( source=source, input_file_name=f"shader.{stage}", entry_point="main", options=options, ) print("Preprocessed source:") print(preprocessed.as_text()) result = compiler.compile_into_spirv( source=source, stage=stage, input_file_name=f"shader.{stage}", entry_point="main", options=options, ) return result.as_binary() except Exception as e: print(f"Compilation failed: {e}") return None shader_with_value = """ #version 450 void main() { float value = SHADER_VALUE; } """ spirv = compile_shader(shader_with_value, "1.0") if spirv is not None: print("Compilation successful")
import shadercrs compiler = shadercrs.Compiler() options = shadercrs.CompileOptions() compute_shader = """ #version 450 layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; layout(set = 0, binding = 0) buffer Data { float values[]; } data; void main() { uint index = gl_GlobalInvocationID.x; data.values[index] *= 2.0; } """ try: result = compiler.compile_into_spirv( source=compute_shader, stage="compute", input_file_name="compute.comp", entry_point="main", options=options, ) spirv_binary = result.as_binary() spirv_bytes = result.as_binary_u8() with open("compute.spv", "wb") as f: f.write(bytes(spirv_bytes)) print(f"Compute shader compiled successfully: {len(spirv_binary)} words") except Exception as e: print(f"Compilation failed: {e}")
import shadercrs def main(): version, revision = shadercrs.get_spirv_version_py() print("SPIR-V version:", version, "revision:", revision) compiler = shadercrs.Compiler() opts = shadercrs.CompileOptions() opts.set_source_language("glsl") opts.set_target_env("vulkan", "vulkan_1_2") opts.set_optimization_level("performance") opts.set_generate_debug_info() glsl_source = """ #version 450 layout(location = 0) in vec3 inPos; void main() { gl_Position = vec4(inPos, 1.0); } """ artifact = compiler.compile_into_spirv( glsl_source, "vertex", "my_shader.glsl", "main", opts ) if artifact.is_empty(): print("Compilation produced empty result!") else: print("Compilation warnings:", artifact.get_num_warnings()) if artifact.get_num_warnings() != 0: print("Warning messages:") print(artifact.get_warning_messages()) spirv_bytes = artifact.as_binary() print("SPIR-V word count:", len(spirv_bytes)) asm_artifact = compiler.compile_into_spirv_assembly(glsl_source, "vertex") asm_text = asm_artifact.as_text() print("Assembly text:\n", asm_text) maybe_profile = shadercrs.parse_version_profile_py("450core") print("Parsed 450core =>", maybe_profile) if __name__ == "__main__": main()
To develop shaderc-rs-py, you can use VS Code in tandem with UV to manage your environment. Run the following commands in your project directory:
curl -LsSf https://astral.sh/uv/install.sh | sh uv self update uv sync source .venv/bin/activate uv add ruff maturin develop --uv
Then open the project in VS Code (or any other editor) and begin making changes. You can run tests (once tests are implemented) by installing pytest inside your environment and executing:
pytest
A real-time shader editing example is located in the "examples" directory. This example uses PySide6 for the GUI and wgpu-py for GPU rendering. It displays a constantly rendered scene in which you can modify a fragment shader and see changes immediately.
Steps to run the example:
-
Ensure you have wgpu-py and PySide6 installed (already part of the requirements in the "pyproject.toml" file).
uv sync
-
Activate your environment and navigate to the "examples" directory.
-
Run the Python script (e.g., "python explorer.py" if that is the entry point).
Once launched, a window will appear where you can adjust the optimization level, edit the shader source, and observe the effects in real-time.