{"id":49714284,"url":"https://github.com/weakknight/nan","last_synced_at":"2026-05-08T19:04:02.510Z","repository":{"id":336825384,"uuid":"1143343718","full_name":"WeakKnight/Nan","owner":"WeakKnight","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-15T19:32:13.000Z","size":9556,"stargazers_count":13,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-15T22:50:27.638Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WeakKnight.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-27T13:17:58.000Z","updated_at":"2026-02-15T15:51:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/WeakKnight/Nan","commit_stats":null,"previous_names":["weakknight/nan"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/WeakKnight/Nan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeakKnight%2FNan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeakKnight%2FNan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeakKnight%2FNan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeakKnight%2FNan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WeakKnight","download_url":"https://codeload.github.com/WeakKnight/Nan/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WeakKnight%2FNan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32793488,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"ssl_error","status_checked_at":"2026-05-08T08:22:45.650Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-08T19:04:01.222Z","updated_at":"2026-05-08T19:04:02.505Z","avatar_url":"https://github.com/WeakKnight.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nan - Python + Slang GPU Path Tracer\n\nAn **educational** real-time GPU path tracing renderer built with SlangPy.\n\n![Demo](demo.png)\n\n## Features\n\n- **Simple unidirectional path tracing** - Easy to understand and extend\n- **Lambert BSDF only** - No complex material models, perfect for learning\n- **Headless mode** - Render without a window, ideal for AI-assisted debugging\n\n## Quick Start\n\n```bash\npip install -r requirements.txt\npython entry_point.py\n```\n\n## Command Line Arguments\n\n| Argument | Description | Default |\n|----------|-------------|---------|\n| `--scene \u003cpath\u003e` | Scene file path | Cornell box |\n| `--headless` | Run without window | - |\n| `--frames \u003cN\u003e` | Number of frames in headless mode | 64 |\n| `--output \u003cpath\u003e` | Output image path for headless mode | headless_output.png |\n| `--width \u003cW\u003e` | Render width | 1920 |\n| `--height \u003cH\u003e` | Render height | 1080 |\n| `--vsync` | Enable V-Sync | - |\n| `--no-srgb` | Keep linear color space in output | - |\n\n## Headless Mode\n\nWindowless batch rendering for offline rendering or CI testing:\n\n```bash\n# Render 128 accumulated frames\npython entry_point.py --headless --frames 128 --output result.png\n\n# Custom resolution\npython entry_point.py --headless --width 3840 --height 2160 --frames 256\n\n# Preserve linear HDR data\npython entry_point.py --headless --no-srgb --output linear.png\n```\n\n## Adding a New Render Pass\n\n### 1. Create Slang Shader\n\n```slang\n// my_pass.slang\nstruct MyPass {\n    Texture2D\u003cfloat4\u003e input;\n    RWTexture2D\u003cfloat4\u003e output;\n\n    void execute(uint2 pixel) {\n        float4 color = input[pixel];\n        // Processing logic\n        output[pixel] = color;\n    }\n}\n\nParameterBlock\u003cMyPass\u003e g_my_pass;\n\n[shader(\"compute\")]\n[numthreads(8, 8, 1)]\nvoid compute_main(uint3 tid: SV_DispatchThreadID) {\n    g_my_pass.execute(tid.xy);\n}\n```\n\n### 2. Create Python Wrapper\n\n```python\n# my_pass.py\nimport slangpy as spy\n\nclass MyPass:\n    def __init__(self, device: spy.Device):\n        self.device = device\n        self.program = device.load_program(\"my_pass.slang\", [\"compute_main\"])\n        self.kernel = device.create_compute_kernel(self.program)\n\n    def execute(\n        self,\n        command_encoder: spy.CommandEncoder,\n        input: spy.Texture,\n        output: spy.Texture,\n    ):\n        self.kernel.dispatch(\n            thread_count=[input.width, input.height, 1],\n            vars={\n                \"g_my_pass\": {\n                    \"input\": input,\n                    \"output\": output,\n                }\n            },\n            command_encoder=command_encoder,\n        )\n```\n\n## Adding a New Renderer\n\nImplement the `Renderer` protocol:\n\n```python\n# my_renderer.py\nimport slangpy as spy\nfrom scene import Scene\nfrom render_data import RenderData\nfrom my_pass import MyPass\nfrom tone_mapper import ToneMapper\n\nclass MyRenderer:\n    def initialize(self, device: spy.Device, scene: Scene):\n        self.device = device\n        self.scene = scene\n        self.my_pass = MyPass(device)\n        self.tone_mapper = ToneMapper(device)\n        \n        # Subscribe to events (optional)\n        scene.event_distpacher.subscribe(\"camera_move\", self.on_camera_move)\n\n    def on_camera_move(self, data):\n        # Handle camera movement\n        pass\n\n    def render(\n        self,\n        command_encoder: spy.CommandEncoder,\n        output: spy.Texture,\n        frame: int,\n        device: spy.Device,\n        scene: Scene,\n        render_data: RenderData,\n    ):\n        # Get/create intermediate textures from render_data\n        temp_texture = render_data.get_texture(\n            \"my_renderer.temp\",\n            width=output.width,\n            height=output.height,\n            format=spy.Format.rgba32_float,\n            usage=spy.TextureUsage.shader_resource | spy.TextureUsage.unordered_access,\n        )\n        \n        # Pass chain\n        self.my_pass.execute(command_encoder, scene.env_map, temp_texture)\n        self.tone_mapper.execute(command_encoder, temp_texture, output)\n\n    def setup_ui(self, ui_context: spy.ui.Context, ui_window: spy.ui.Window):\n        # Add UI controls (optional)\n        pass\n```\n\nUse in `entry_point.py`:\n\n```python\nfrom my_renderer import MyRenderer\n\ndef main():\n    renderer = MyRenderer()\n    app = App(config=config)\n    app.set_renderer(renderer)\n    app.main_loop()\n```\n\n## Hotkeys\n\n| Key | Function |\n|-----|----------|\n| `WASD` + Mouse | Camera control |\n| `F1` | TEV viewer |\n| `F2` | Screenshot |\n| `F11` | RenderDoc capture |\n| `Esc` | Quit |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweakknight%2Fnan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fweakknight%2Fnan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fweakknight%2Fnan/lists"}