Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies = [
"requests",
"numpy",
"Pillow",
"av"
]
description = "Shadertoy implementation based on wgpu-py"
license = {file = "LICENSE"}
Expand Down
1 change: 1 addition & 0 deletions wgpu_shadertoy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .inputs import ShadertoyChannel, ShadertoyChannelBuffer, ShadertoyChannelTexture
from .passes import BufferRenderPass, ImageRenderPass
from .record import record_offscreen
from .shadertoy import Shadertoy

__version__ = "0.2.0"
Expand Down
67 changes: 64 additions & 3 deletions wgpu_shadertoy/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import argparse
import os

from .shadertoy import Shadertoy
from .record import record_offscreen

Check failure on line 5 in wgpu_shadertoy/cli.py

View workflow job for this annotation

GitHub Actions / Test Linting

Ruff (I001)

wgpu_shadertoy/cli.py:1:1: I001 Import block is un-sorted or un-formatted

argument_parser = argparse.ArgumentParser(
description="Download and render Shadertoy shaders"
Expand All @@ -9,21 +11,80 @@
argument_parser.add_argument(
"shader_id", type=str, help="The ID of the shader to download and render"
)
# shared args
argument_parser.add_argument(
"--resolution",
type=int,
nargs=2,
help="The resolution to render the shader at",
default=(800, 450),
)
# maybe put framerate here

command_parser = argument_parser.add_subparsers(dest="command", help="subcommands for this CLI")

show_parser = command_parser.add_parser(
"show",
help="display the shader in a GUI (default)"
)
# TODO vsync, max framerate(?), gui lib, maybe offsets?

record_parser = command_parser.add_parser(
"record",
help="records shader to a video file (offscreen)"
)
record_parser.add_argument(
"--output_file",
type=str,
default=None, #maybe the shader id or name or something?
help="The output file to save the recorded video"
)
record_parser.add_argument(
"--start_offset",
type=float,
default=0.0,
help="the starting iTime, not prerendering frames", # maybe worth it for accumulation?
)
record_parser.add_argument(
"--duration",
type=float,
default=10.0,
help="The duration of the recorded video in seconds, defaults to 10.0",
)
record_parser.add_argument(
"--framerate",
type=int,
default=60,
help="The framerate of the recorded video, defaults to 60",
)
record_parser.add_argument(
"--target_size",
type=float,
default=9.9,
help="The target size of the recorded video, defaults to 9.9 MB",
)
# maybe bitrate too?

def main_cli():
args = argument_parser.parse_args()
shader_id = args.shader_id
shader_id = args.shader_id.rstrip('/').split('/')[-1]
resolution = args.resolution
shader = Shadertoy.from_id(shader_id, resolution=resolution)
shader.show()
if args.command == "record":
recording_args = {
"start_offset": args.start_offset,
"duration": args.duration,
"framerate": args.framerate,
"target_size": args.target_size,
"output_file": args.output_file or f"{shader_id}.mp4",
}
# TODO: replace resolution with padded variant here?
shader = Shadertoy.from_id(shader_id, resolution=resolution, offscreen=True)
record_offscreen(shader, **recording_args)
print(f"Recording finished: {os.getcwd()}/{recording_args['output_file']}")
else:
# gui-args = ?
shader = Shadertoy.from_id(shader_id, resolution=resolution)
shader.show()


if __name__ == "__main__":
Expand Down
3 changes: 2 additions & 1 deletion wgpu_shadertoy/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def __init__(self, data=None, **kwargs):
if len(self.data.shape) == 2:
self.data = np.reshape(self.data, self.data.shape + (1,))
# greyscale textures become just red while green and blue remain 0s
# TODO: can just use r8unorm and the sampler will return vec4 anyway
if self.data.shape[2] == 1:
self.data = np.stack(
[
Expand Down Expand Up @@ -310,7 +311,7 @@ def bind_texture(self, device: wgpu.GPUDevice) -> Tuple[list, list]:
binding_layout = self._binding_layout()
texture = device.create_texture(
size=self.texture_size,
format=wgpu.TextureFormat.rgba8unorm,
format=wgpu.TextureFormat.rgba8unorm, #TODO: this might be dependant on the file we get
usage=wgpu.TextureUsage.TEXTURE_BINDING | wgpu.TextureUsage.COPY_DST,
)

Expand Down
Loading
Loading