diff --git a/examples/shadertoy_glsl_clock.py b/examples/shadertoy_glsl_clock.py new file mode 100644 index 00000000..8ebb5c2d --- /dev/null +++ b/examples/shadertoy_glsl_clock.py @@ -0,0 +1,94 @@ +from wgpu.utils.shadertoy import Shadertoy + +shader_code = """ +// source: https://www.shadertoy.com/view/MdVcRd + +// See https://www.shadertoy.com/view/ldKGRR + +// Computes a smooth-edged diamond pixel value (Manhattan distance) +#define P(i, j, b) \ + vec2(.1, b).xyxy * smoothstep(0., 9. / R.y, .1 - abs(i) - abs(j)) + +// Computes a segment value (length = 0.5) +#define S(i, j, b) \ + P(i - clamp(i, 0., .5), j, b & 1) + +// Colon render +#define C \ + x += .5; O += P(x, y + .3, i.w / 50) + P(x, y - .3, i.w / 50); t /= 60 + +// Hyphen render +#define H(b) \ + ++x; O += S(x, y, b) + +// Computes the horizontal and vertical segments based on a denary digit +#define X(i, j, b) \ + S(x - i, y - j, b) +#define Y(i, j, b) \ + S(y - j, x - i, b) +#define D(n) \ + H(892>>n) \ + + X(0., .7, 1005>>n) \ + + X(0., -.7, 877>>n) \ + + Y(-.1, .1, 881>>n) \ + + Y(.6, .1, 927>>n) \ + + Y(-.1, -.6, 325>>n) \ + + Y(.6, -.6, 1019>>n); + +// Two-digit render +#define Z(n) ; D(n % 10) D(n / 10) + +void mainImage(out vec4 O, vec2 U) +{ + vec2 R = iResolution.xy; + U += U - R; + U /= R.y / 3.; // Global scaling with aspect ratio correction + O-=O; // Zero the pixel + + float x = U.x - U.y * .2 - 2.8, // Slight skew to slant the digits + y = --U.y; + ivec4 i = ivec4(iDate); // Convert everything to integers + int t = i.w; + i.w = int(iDate.w * 100.) % 100 // Replace with centiseconds + + // Seconds (preceded by a colon) + Z(t % 60) + C + + // Minutes (preceded by a colon) + Z(t % 60) + C + + // Hours + Z(t) + + // Smaller digits + x /= .6; + y /= .6; + R *= .6; + + // Centiseconds + x -= 14.; + y += .53 + Z(i.w) + + // Day (preceded by a hyphen) + x -= .8; + y += 3. + Z(i.z) + H(1) + + // Month (preceded by a hyphen) + Z((i.y + 1)) // Is it a bug in shadertoy that we have to add one? + H(1) + + // Year + Z(i.x % 100) + Z(i.x / 100) +} + +""" # noqa +shader = Shadertoy(shader_code) + +if __name__ == "__main__": + shader.show() diff --git a/wgpu/utils/shadertoy.py b/wgpu/utils/shadertoy.py index 997434ac..dc25063d 100644 --- a/wgpu/utils/shadertoy.py +++ b/wgpu/utils/shadertoy.py @@ -29,18 +29,20 @@ builtin_variables_glsl = """ #version 450 core -vec3 i_resolution; vec4 i_mouse; +vec4 i_date; +vec3 i_resolution; float i_time; float i_time_delta; int i_frame; // Shadertoy compatibility, see we can use the same code copied from shadertoy website -#define iTime i_time +#define iMouse i_mouse +#define iDate i_date #define iResolution i_resolution +#define iTime i_time #define iTimeDelta i_time_delta -#define iMouse i_mouse #define iFrame i_frame #define mainImage shader_main @@ -52,6 +54,7 @@ struct ShadertoyInput { vec4 mouse; + vec4 date; vec3 resolution; float time; float time_delta; @@ -62,13 +65,13 @@ out vec4 FragColor; void main(){ - i_time = input.time; + i_mouse = input.mouse; + i_date = input.date; i_resolution = input.resolution; + i_time = input.time; i_time_delta = input.time_delta; - i_mouse = input.mouse; i_frame = input.frame; - vec2 uv = vec2(uv.x, 1.0 - uv.y); vec2 frag_coord = uv * i_resolution.xy; @@ -107,8 +110,9 @@ builtin_variables_wgsl = """ -var i_resolution: vec3; var i_mouse: vec4; +var i_date: vec4; +var i_resolution: vec3; var i_time_delta: f32; var i_time: f32; var i_frame: u32; @@ -123,6 +127,7 @@ struct ShadertoyInput { mouse: vec4, + date: vec4, resolution: vec3, time: f32, time_delta: f32, @@ -141,10 +146,11 @@ @fragment fn main(in: Varyings) -> @location(0) vec4 { - i_time = input.time; + i_mouse = input.mouse; + i_date = input.date; i_resolution = input.resolution; + i_time = input.time; i_time_delta = input.time_delta; - i_mouse = input.mouse; i_frame = input.frame; @@ -169,6 +175,8 @@ class UniformArray: """Convenience class to create a uniform array. Maybe we can make it a public util at some point. + Ensure that the order matches structs in the shader code. + See https://www.w3.org/TR/WGSL/#alignment-and-size for reference on alignment. """ def __init__(self, *args): @@ -239,8 +247,9 @@ class Shadertoy: * ``i_frame``: the frame number * ``i_resolution``: the resolution of the shadertoy * ``i_mouse``: the mouse position in pixels + * ``i_date``: the current date and time as a vec4 (year, month, day, seconds) - For GLSL, you can also use the aliases ``iTime``, ``iTimeDelta``, ``iFrame``, ``iResolution``, and ``iMouse`` of these built-in variables, + For GLSL, you can also use the aliases ``iTime``, ``iTimeDelta``, ``iFrame``, ``iResolution``, ``iMouse`` and ``iDate`` of these built-in variables, the entry point function also has an alias ``mainImage``, so you can use the shader code copied from shadertoy website without making any changes. """ @@ -251,6 +260,7 @@ class Shadertoy: def __init__(self, shader_code, resolution=(800, 450), offscreen=False) -> None: self._uniform_data = UniformArray( ("mouse", "f", 4), + ("date", "f", 4), ("resolution", "f", 3), ("time", "f", 1), ("time_delta", "f", 1), @@ -429,6 +439,17 @@ def _update(self): if not hasattr(self, "_frame"): self._frame = 0 + time_struct = time.localtime() + self._uniform_data["date"] = ( + float(time_struct.tm_year), + float(time_struct.tm_mon - 1), + float(time_struct.tm_mday), + time_struct.tm_hour * 3600 + + time_struct.tm_min * 60 + + time_struct.tm_sec + + now % 1, + ) + self._uniform_data["frame"] = self._frame self._frame += 1