diff options
| -rw-r--r-- | alv-lib/glsl-view.moon | 417 | ||||
| -rw-r--r-- | alv-lib/osc.moon | 8 | ||||
| -rw-r--r-- | alv-lib/wgsl-view.moon | 327 | ||||
| -rw-r--r-- | examples/glsl-view/simple.alv | 12 | ||||
| -rw-r--r-- | examples/wgsl-view/simple.alv | 15 | ||||
| -rw-r--r-- | spec/lib/wgsl-view_spec.moon | 115 |
6 files changed, 461 insertions, 433 deletions
diff --git a/alv-lib/glsl-view.moon b/alv-lib/glsl-view.moon deleted file mode 100644 index 6142502..0000000 --- a/alv-lib/glsl-view.moon +++ /dev/null @@ -1,417 +0,0 @@ -import Error, Constant, PureOp, Op, template_subst, Input, T, Array, Struct, sig, evt, const, any from require 'alv.base' -import Message, Bundle, Timetag, add_item from require 'alv-lib._osc' -import dns, udp from require 'socket' - -fmttag = (tag) -> tag\ident!\gsub '[%.:%-/\\]', '_' - -tryprefix = (prefix, filename) -> - return filename unless prefix and filename\match '^%./' - prefix .. filename - -GLSL_TYPES = - num: 'float' - bool: 'bool' - -for i=2,4 - GLSL_TYPES["num[#{i}]"] = "vec#{i}" - GLSL_TYPES["bool[#{i}]"] = "bvec#{i}" - - for j=2,4 - GLSL_TYPES["num[#{j}][#{i}]"] = "mat#{i}x#{j}" - -GLSL_SAMPLER_TYPES = - "1D": 'sampler1D' - "2D": 'sampler2D' - "3D": 'sampler3D' - "CUBE_MAP": 'samplerCube' - "RECTANGLE": 'sampler2DRect' - "1D_ARRAY": 'sampler1DArray' - "2D_ARRAY": 'sampler2DArray' - "CUBE_MAP_ARRAY": 'samplerCubeArray' - "BUFFER": 'samplerBuffer' - -Uniform = Struct type: T.str, val: T.str - -get_typestr = (type) -> - if typestr = GLSL_TYPES[tostring type] - return typestr - - if type.__class == Array - if typestr = GLSL_TYPES[tostring type.type] - return "#{typestr}[#{type.size}]" - - error Error 'type', "can't wrap #{type} in uniform" - -uniform = Constant.meta - meta: - name: 'uniform' - summary: "override uniform binding type." - examples: { '(uniform [glsl-type] value)' } - description: "Label an alive value with a GLSL type: -`(uniform 'sampler2D' 'sampler-name')` is equivalent to `{'type' 'sampler2D' 'val' 'sampler-name'}`. - -When absent, `glsl-type` is inferred based on the type of `value`: -- `num`, `bool`: `uniform float/bool` -- `num[I]`, `bool[I]`: `uniform vecI/bvecI` -- `num[I][J]`: `uniform matIxJ`" - - value: class extends PureOp - pattern: -sig.str + sig! - type: (inputs) => Struct type: T.str, val: inputs[2]\type! - tick: => - { type, val } = @unwrap_all! - type or= get_typestr @inputs[2]\type! - @out\set :type, :val - -offset= Constant.meta - meta: - name: 'offset' - summary: "get the playback offset of a buffered-stream-source." - examples: { '(offset [glsl-type] source)' } - description: "GLSL type: float/double/int/uint" - - value: class extends PureOp - pattern: -sig.str + sig! - type: => Uniform - tick: => - { type, source } = @unwrap_all! - type or= "float" - name = source.val - @out\set :type, val: "/source/#{name}/offset" - -video_source = Constant.meta - meta: - name: 'video-source' - summary: "load a video texture source." - examples: { '(video-source [socket] type filename [format] [args])' } - description: "Creates a texture source from a video or image file. - -- `type` can be one of: - - `2D`: `sampler2D` (loads first frame only) - - `3D` or `2D_ARRAY`: `sampler3D`/`sampler2DArray` (loads whole video) -- `filename` is the video file to load. If it starts with `./` it will be - resolved relative to the current alv module. -- `format` optionally specifies the ffmpeg/avformat input format to use. -- `args` is a struct of optional arguments for the ffmpeg/avformat input format." - - value: class extends Op - pattern = -sig['osc/out'] + const.str + const.str + -const.str + -const! - setup: (inputs, scope) => - { socket, type, filename, format, args } = pattern\match inputs - - @prefix = COPILOT.active_module.file\match'(.*/)[^/]*$' - - super - socket: Input.hot socket or scope\get '*oscout*' - type: Input.cold type - filename: Input.cold filename - format: format and Input.cold format - args: args and Input.cold args - - samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] - @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" - - tick: => - { :socket, :type, :filename, :format, :args } = @unwrap_all! - - with @inputs.socket! - filename = tryprefix @prefix, filename - msg = Message.new{ address: "/source/alv-#{fmttag @tag}/video", types: "ss", "TEXTURE_#{type}", filename } - msg\add 's', format if format - add_item msg, @inputs.args\type!, args if args - \send Message.pack msg.content - - -- destroy: => - -- name = @inputs.name! - -- with @inputs.socket! - -- \send Message.pack { address: "/source/#{name}/destroy", types: "" } - -stream_source = Constant.meta - meta: - name: 'stream-source' - summary: "load a stream texture source." - examples: { '(stream-source [socket] type uri [format] [args])' } - description: "Creates a realtime stream from a capture source. - -- `type` must be `2D`. -- `uri` is the ffmpeg URI to load. If it starts with `./` it will be resolved relative to the current alv module. -- `format` optionally specifies the ffmpeg/avformat input format to use. -- `args` is a struct of optional arguments for the ffmpeg/avformat input format. - - (stream-source 'webcam' '2D' '/dev/video0') - (stream-source 'testpt' '2D' 'testsrc=size=1280x720:rate=30' 'lavfi')" - - value: class extends Op - pattern = -sig['osc/out'] + const.str + const.str + -const.str + -const! - setup: (inputs, scope) => - { socket, type, filename, format, args } = pattern\match inputs - - @prefix = COPILOT.active_module.file\match'(.*/)[^/]*$' - - super - socket: Input.hot socket or scope\get '*oscout*' - type: Input.cold type - filename: Input.cold filename - format: format and Input.cold format - args: args and Input.cold args - - samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] - @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" - - tick: => - { :socket, :name, :type, :filename, :format, :args } = @unwrap_all! - - with @inputs.socket! - filename = tryprefix @prefix, filename - msg = Message.new{ address: "/source/alv-#{fmttag @tag}/stream", types: "ss", "TEXTURE_#{type}", filename } - msg\add 's', format if format - add_item msg, @inputs.args\type!, args if args - \send Message.pack msg.content - -buffered_stream_source = Constant.meta - meta: - name: 'buffered-stream-source' - summary: "load a stream as a 3d texture source." - examples: { '(buffered-stream-source [socket] type depth uri [format] [args])' } - description: "Creates a circular buffer 3d texture from a capture source. - -- `type` must be `3D` or `2D_ARRAY`. -- `depth` is the number of frames to keep in the buffer. -- `uri` is the ffmpeg URI to load. If it starts with `./` it will be resolved relative to the current alv module. -- `format` optionally specifies the ffmpeg/avformat input format to use. -- `args` is a struct of optional arguments for the ffmpeg/avformat input format. - - (stream-source 'webcam' '3D' 64 '/dev/video0')" - - value: class extends Op - pattern = -sig['osc/out'] + const.str + const.num + const.str + -const.str + -const! - setup: (inputs, scope) => - { socket, type, depth, filename, format, args } = pattern\match inputs - - @prefix = COPILOT.active_module.file\match'(.*/)[^/]*$' - - super - socket: Input.hot socket or scope\get '*oscout*' - type: Input.cold type - depth: Input.cold depth - filename: Input.cold filename - format: format and Input.cold format - args: args and Input.cold args - - samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] - @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" - - tick: => - { :socket, :name, :type, :depth, :filename, :format, :args } = @unwrap_all! - - with @inputs.socket! - filename = tryprefix @prefix, filename - msg = Message.new{ address: "/source/alv-#{fmttag @tag}/buffered-stream", types: "sis", "TEXTURE_#{type}", depth, filename } - msg\add 's', format if format - add_item msg, @inputs.args\type!, args if args - \send Message.pack msg.content - -tsv_source = Constant.meta - meta: - name: 'tsv-source' - summary: "load a texture-share-vk texture source." - examples: { '(tsv-source [socket] name [type])' } - description: "Creates a texture source from a texture-share-vk texture. - -- `name` is the tsv name of this texture source. -- `type` defaults to `2D`." - - value: class extends Op - pattern = -sig['osc/out'] + const.str + -const.str - setup: (inputs, scope) => - { socket, name, type } = pattern\match inputs - - super - socket: Input.hot socket or scope\get '*oscout*' - name: Input.hot name - type: Input.hot type or Constant.str "2D" - - samplertype = assert GLSL_SAMPLER_TYPES[@inputs.type!] - @setup_out '=', Uniform, type: samplertype, val: "alv-#{fmttag @tag}" - - tick: => - { :socket, :name, :type } = @unwrap_all! - - with @inputs.socket! - \send Message.pack { address: "/source/alv-#{fmttag @tag}/tsv", types: "ss", "TEXTURE_#{type}", name } - -freeze = Constant.meta - meta: - name: 'freeze' - summary: "freeze a texture source." - examples: { '(freeze [socket] source freeze?)' } - - value: class extends Op - pattern = -sig['osc/out'] + const[Uniform] + any.bool - setup: (inputs, scope) => - { socket, source, freeze } = pattern\match inputs - - super - socket: Input.hot socket or scope\get '*oscout*' - source: Input.cold source - freeze: Input.hot freeze - - tick: => - { :socket, :source, :freeze } = @unwrap_all! - - with @inputs.socket! - msg = Message.new{ address: "/source/#{source.val}/freeze", types: "" } - add_item msg, @inputs.freeze\type!, freeze - \send Message.pack msg.content - -step_ = Constant.meta - meta: - name: 'step!' - summary: "step a frozen texture source." - examples: { '(step! [socket] source [trig!])' } - - value: class extends Op - pattern = -sig['osc/out'] + any[Uniform] + -evt.bang - setup: (inputs, scope) => - { socket, source, trig } = pattern\match inputs - - super - socket: Input.hot socket or scope\get '*oscout*' - source: Input.hot source - trig: trig and Input.hot trig - - tick: => - { :socket, :source, :trig } = @unwrap_all! - - with @inputs.socket! - \send Message.pack { address: "/source/#{source.val}/step", types: "I" } - -shader_ = Constant.meta - meta: - name: 'shader' - summary: "compile a GLSL shader with uniforms." - examples: { '$shader"code…"' } - description: "A template string formatter for [glsl-view/draw][] that returns a shader struct~. - -- `code…` is a template string that can contain `~-stream` template substitutions that are used as follows: - - `str`: substituted directly as GLSL - - another shader struct~: substituted as GLSL, with uniforms lifted - - `{type: str val: …}`: reference to uniform of GLSL-type `type` - - other values are implicitly converted as by [glsl-view/uniform][]" - - value: class extends PureOp - pattern: const! + sig!^0 - - name: (i) => "alv_u#{fmttag @tag}_#{i}" - - type: (args) => - uniforms = {} - for i, val in ipairs args[2] - type = val\type! - if type == T.str - continue - else if type.__class == Struct and type.types['shader'] - for name, typ in pairs type.types['uniforms'].types - uniforms[name] = typ - else - if type.__class == Struct - assert type.types['type'] == T.str, Error 'type', "struct substituton needs type key" - else - type = Struct type: T.str, val: type - - uniforms[@name i] = type - - Struct shader: T.str, uniforms: Struct uniforms - - tick: => - uniforms = {} - - pieces = for i, input in ipairs @inputs[2] - name, input = (@name i), input - val, type = input!, input\type! - - if type == T.str - val - elseif type.__class == Struct and type.types['shader'] - for k, v in pairs val.uniforms - uniforms[k] = v - val.shader - else - if type.__class != Struct - val = :val, type: get_typestr type - - uniforms[name] = val - name - - shader = template_subst @inputs[1]!, pieces - - @out\set :shader, :uniforms - -draw = Constant.meta - meta: - name: 'draw' - summary: "draw a GLSL shader via OSC." - examples: { '(draw [socket] shader)' } - description: "Syncs code and parameters with glsl-view via OSC. - -- `socket` must be an `osc/out~` or read from `*oscout*` -- `shader` is a struct with - - `shader` (`str`): GLSL source - - `uniforms`: struct mapping uniform names to values" - - value: class extends Op - pattern = -sig['osc/out'] + any! - - setup: (inputs, scope) => - { socket, shader } = pattern\match inputs - super - socket: Input.hot socket or scope\get '*oscout*' - shader: Input.hot shader - - tick: => - { :socket, :shader } = @unwrap_all! - - if not shader - return - - with Bundle.new Timetag.new! - chunks = { '#version 330 core\n' } - for key, val in pairs shader.uniforms - typ, arrsuff = val.type\match "^(%w+)(%[%d+%])$" - table.insert chunks, if typ and arrsuff - print "typ and arrsuf", typ, arrsuff - "uniform #{typ} #{key}#{arrsuff};" - else - "uniform #{val.type} #{key};" - table.insert chunks, shader.shader - - \add content: { address: '/shader', types: 's', table.concat chunks, '\n' } - - types = @inputs.shader\type!\get 'uniforms' - for key, val in pairs shader.uniforms - type = types\get(key)\get 'val' - msg = Message.new "/uniform/#{key}" - add_item msg, type, val.val - \add msg - - socket\send Bundle.pack .content - -Constant.meta - meta: - name: 'glsl-view' - summary: "glsl-view integration via OSC." - - value: - :uniform - - 'video-source': video_source - 'stream-source': stream_source - 'buffered-stream-source': buffered_stream_source - 'tsv-source': tsv_source - - :offset - - :freeze, 'step!': step_ - - shader: shader_ - :draw diff --git a/alv-lib/osc.moon b/alv-lib/osc.moon index 82be97e..0e51b40 100644 --- a/alv-lib/osc.moon +++ b/alv-lib/osc.moon @@ -1,6 +1,6 @@ import Op, PureOp, Constant, Input, T, Array, sig, const, any from require 'alv.base' import new_message, add_item, Message from require 'alv-lib._osc' -import dns, udp from require 'socket' +socket = require 'socket' losc = require 'losc' losc_Pattern = require 'losc.pattern' @@ -25,9 +25,9 @@ connect = Constant.meta tick: => { :host, :port } = @unwrap_all! - ip = dns.toip host + ip = socket.dns.toip host - @out\set with sock = udp! + @out\set with sock = socket.udp! \setpeername ip, port listen = Constant.meta @@ -67,7 +67,7 @@ listen = Constant.meta tick: => if @inputs.port\dirty! or @inputs.host\dirty! - @state = with udp! + @state = with socket.udp! \settimeout 0 \setoption 'reuseaddr', true \setoption 'reuseport', true diff --git a/alv-lib/wgsl-view.moon b/alv-lib/wgsl-view.moon new file mode 100644 index 0000000..02d27b4 --- /dev/null +++ b/alv-lib/wgsl-view.moon @@ -0,0 +1,327 @@ +import RTNode, Error, Constant, PureOp, Op, template_subst, Input, T, Primitive, Array, Struct, sig, evt, const, any from require 'alv.base' +import Message, Bundle, Timetag from require 'alv-lib._osc' + +SCALAR_TYPES = num: 'f32', bool: 'u32' +get_typestr = (type) -> + typestr = tostring type + + if wgsl = typestr\match "^wgsl/(.+)$" + return wgsl + + if scalar = SCALAR_TYPES[typestr] + return scalar + + if type.__class == Array + vecsz = type.size >= 2 and type.size <= 4 and type.size + if inner = vecsz and SCALAR_TYPES[tostring type.type] + return "vec#{vecsz}<#{inner}>" + + inner = get_typestr type.type + return "array<#{inner}, #{type.size}>" + + error Error 'type', "can't convert #{type} to WGSL type" + +fmttag = (tag) -> tag\ident!\gsub '[%.:%-/\\]', '_' +add_item = (message, val, use_arrays=true) -> + message\add switch type val + when "number" then 'f', val + when "string" then 's', val + when "boolean" + if val then 'T' else 'F' + when "table" + message\add '[' if use_arrays + for inner in *val + add_item message, inner, use_arrays + message\add ']' if use_arrays + return + else + error "unable to send #{val}" + +class Shader + is_bindable = (typestr) -> + typestr == "sampler" or typestr\match "^texture_" + + new: (@id, code, inputs) => + @uniforms = {} + @bindings = {} + @deps = {} + + pieces = for i, input in ipairs inputs + val, type = input!, input\type! + + if type == T.str + val + elseif type == T["wgsl-v/shader"] + @deps[val.id] = val + "package::#{val.id}" + elseif type.__class == Primitive + typestr = get_typestr tostring type + if is_bindable typestr + table.insert @bindings, { :val, :typestr } + "b_#{#@bindings}" + else + table.insert @uniforms, { :val, :typestr } + "u._#{#@uniforms}" + + @code = template_subst code, pieces + + compile: (g, bi) => + chunks = { @code, "" } + if #@uniforms > 0 + table.insert chunks, "struct Uniforms {" + for i, uniform in pairs @uniforms + table.insert chunks, " _#{i}: #{uniform.typestr}," + table.insert chunks, "};" + table.insert chunks, "@group(#{g}) @binding(#{bi}) var<uniform> u: Uniforms;" + bi += 1 + + for i, binding in pairs @bindings + table.insert chunks, "@group(#{g}) @binding(#{bi}) var b_#{i}: #{binding.typestr};" + bi += 1 + + code = table.concat chunks, "\n" + code, bi + + add_module_message: (bundle, g, bi) => + code, bi = @compile g, bi + bundle\add content: { address: "/module/package::#{@id}", types: 's', code } + bi + + add_binding_messages: (bundle) => + for i, binding in ipairs @bindings + bundle\add Message.new { address: "/binding/package::#{@id}/b_#{i}", types: "s", binding.val } + + for i, uniform in ipairs @uniforms + msg = Message.new "/uniform/package::#{@id}/u/_#{i}" + add_item msg, uniform.val + bundle\add msg + + all_modules: (all={})=> + all[@id] = @ + + for k, v in pairs @deps + v\all_modules all + + all + + @code_eql: (a, b) -> + return false if not a or not b + return false unless a.code == b.code + + for k, v in pairs a.deps + return false unless v == b[k] + + for k, v in pairs b.deps + return false unless a[k] + + return true + +sampler = Constant.meta + meta: + name: 'sampler' + summary: "create a sampler." + examples: { '(sampler [socket] filter-mode address-mode)' } + description: 'Creates a sampler resource. + +- `filter-mode` is one of `"linear"` or `"nearest"` +- `address-mode` is one of `"clamp"`, `"repeat"` or `"mirror"`' + + value: class extends Op + pattern = -sig['osc/out'] + sig.str + sig.str + setup: (inputs, scope) => + { socket, filter, address } = pattern\match inputs + + super + socket: Input.hot socket or scope\get '*oscout*' + filter: Input.hot filter + address: Input.hot address + + @state = "/sampler/alv-#{fmttag @tag}" + @setup_out '=', T["wgsl/sampler"], @state + + tick: => + { :socket, :filter, :address } = @unwrap_all! + + with @inputs.socket! + \send Message.pack { address: @state, types: "ss", filter, address } + + destroy: => + return unless @state + with @inputs.socket! + \send Message.pack { address: "#{@state}/destroy", types: "" } + +video_stream = Constant.meta + meta: + name: 'video-stream' + summary: "create a video stream" + examples: { '(video-stream [socket] args…)' } + description: 'Creates a video stream from ffmpeg args.' + + value: class extends Op + pattern = -sig['osc/out'] + const!^0 + default_prefix = RTNode result: Constant.str "" + setup: (inputs, scope) => + { socket, rest } = pattern\match inputs + + super + socket: Input.hot socket or scope\get '*oscout*' + rest: [Input.hot p for p in *rest] + + bin_prefix = (scope\get '*wgsl-view-bin*', default_prefix).result! + + tsvname = "alv-#{fmttag @tag}" + id = "/texture/#{tsvname}" + args = table.concat @unwrap_all!.rest, ' ' + @state or= { + :tsvname, :id + pipe: io.popen bin_prefix .. "tsv-video-stream --name #{tsvname} -- #{args}", + } + @setup_out '=', T["wgsl/texture_2d<f32>"], id + + tick: => + with @inputs.socket! + \send Message.pack { address: @state.id, types: "s", @state.tsvname } + + destroy: => + return unless @state + with @inputs.socket! + \send Message.pack { address: "#{@state.id}/destroy", types: "" } + +tsv_tex = Constant.meta + meta: + name: 'tsv-tex' + summary: "load a TSV texture" + examples: { '(tsv-tex [socket] type name)' } + description: 'Load the TSV texture `name` of the given type (`1d`, 2d`, `2d_array` or `3d`).' + + value: class extends Op + pattern = -sig['osc/out'] + const.str + sig.str + default_prefix = RTNode result: Constant.str "" + setup: (inputs, scope) => + { socket, typename, tsvname } = pattern\match inputs + + super + socket: Input.hot socket or scope\get '*oscout*' + typename: Input.cold typename + tsvname: Input.hot tsvname + + @state = "/texture/alv-#{fmttag @tag}" + @setup_out '=', T["wgsl/texture_#{@inputs.typename!}<f32>"], @state + + tick: => + { :socket, :tsvname } = @unwrap_all! + with @inputs.socket! + \send Message.pack { address: @state, types: "s", tsvname } + + destroy: => + return unless @state + with @inputs.socket! + \send Message.pack { address: "#{@state}/destroy", types: "" } + +cast = Constant.meta + meta: + name: 'cast' + summary: "cast alive values to WGSL types." + examples: { '(cast [wgsl-type] value)' } + description: "Label an alive value with a WGSL type.: +When absent, `wgsl-type` is inferred based on the type of `value`: +- `num`: `f32` +- `bool`: `u32` +- `num[I]`: `vecI<f32>` (if `I` is in 2-4) +- `B[I]`: `array<B, I>` (where B is cast by the same rules)" + + value: class extends PureOp + pattern: -sig.str + sig! + + type: (inputs) => + typestr = if inputs[1] then inputs[1].result! + else get_typestr inputs[2]\type! + T["wgsl/" .. typestr] + + tick: => @out\set @inputs[2]! + +shader_ = Constant.meta + meta: + name: 'shader' + summary: "compile a WGSL shader with uniforms." + examples: { '$shader"code…"' } + description: "A template string formatter for [wgsl-view/draw][] that returns a shader struct~. + +- `code…` is a template string that can contain `~-stream` template substitutions that are used as follows: + - `str`: substituted directly as WGSL + - another shader struct~: substituted as WGSL, with uniforms lifted + - `{type: str val: …}`: reference to uniform of WGSL-type `type` + - other values are implicitly converted as by [wgsl-view/cast][]" + + value: class extends PureOp + pattern: const! + sig!^0 + type: T["wgsl-v/shader"] + tick: => + shader = Shader "alv_#{fmttag @tag}", @inputs[1]!, @inputs[2] + @out\set shader + +draw = Constant.meta + meta: + name: 'draw' + summary: "draw a WGSL shader via OSC." + examples: { '(draw [socket] shader)' } + description: "Syncs code and parameters with wgsl-view via OSC. + +- `socket` must be an `osc/out~` or read from `*oscout*` +- `shader` is a struct with + - `shader` (`str`): WGSL source + - `uniforms`: struct mapping uniform names to values" + + value: class extends Op + pattern = -sig['osc/out'] + any! + + setup: (inputs, scope) => + { socket, shader } = pattern\match inputs + + super + socket: Input.hot socket or scope\get '*oscout*' + shader: Input.hot shader + + @state or= {} + + tick: => + { :socket, :shader } = @unwrap_all! + + if not shader + return + + bundle = Bundle.new Timetag.new! + values = Bundle.new Timetag.new! + + g, bi = 1, 0 + dirty = false + for k, module in pairs shader\all_modules! + bi = module\add_module_message bundle, g, bi + module\add_binding_messages values + + if not Shader.code_eql @state[k], module + dirty = true + @state[k] = module + + if dirty + bundle\add content: { address: '/entrypoint', types: 's', "package::#{shader.id}" } + + bundle\add values + socket\send Bundle.pack bundle.content + +Constant.meta + meta: + name: 'wgsl-view' + summary: "wgsl-view integration via OSC." + + value: + '*wgsl-view-bin*': Constant.str '' + + :cast + :sampler + + 'tsv-tex': tsv_tex + 'video-stream': video_stream + + shader: shader_ + :draw diff --git a/examples/glsl-view/simple.alv b/examples/glsl-view/simple.alv deleted file mode 100644 index 02c8796..0000000 --- a/examples/glsl-view/simple.alv +++ /dev/null @@ -1,12 +0,0 @@ -([1]import* glsl-view time math) -([2]import osc) - -([4]def *oscout* ([3]osc/connect 'localhost' 9000)) - -([7]draw $[6]shader" -in vec2 uv; -out vec4 color; - -void main() { - color = vec4(uv, sin(uv.x + $([5]ramp 1 tau)), 1); -}") diff --git a/examples/wgsl-view/simple.alv b/examples/wgsl-view/simple.alv new file mode 100644 index 0000000..ff66602 --- /dev/null +++ b/examples/wgsl-view/simple.alv @@ -0,0 +1,15 @@ +([1]import* wgsl-view time math) +([2]import osc) + +([4]def *oscout* ([3]osc/connect 'localhost' 9000)) + +## let cols = $[[1 0 0] [0 1 0] [0 0 1]]; +## let col = cols[i32($([8]tick 0.5)) % 3]; + +([7]draw $[6]shader" +@fragment +fn fs_main(@location(0) _uv: vec2f) -> @location(0) vec4f { + var uv = _uv * 2.0 - 1.0; + let col = 0.5 + 0.5 * cos($([5]ramp 1 tau) + uv.xyx + vec3f(0, 2, 4)); + return vec4f(col, 1.0); +}") diff --git a/spec/lib/wgsl-view_spec.moon b/spec/lib/wgsl-view_spec.moon new file mode 100644 index 0000000..3676b63 --- /dev/null +++ b/spec/lib/wgsl-view_spec.moon @@ -0,0 +1,115 @@ +import TestPilot from require 'spec.test_setup' +import T, Array from require 'alv' +import _ from require "luassert.match" + +socket = require "socket" + +describe "wgsl-view", -> + test = TestPilot '', ' +(import* wgsl-view) +(import osc) +(def *oscout* (osc/connect "localhost" 9000)) +' + + local SOCK + _udp = socket.udp + stub socket, 'udp', -> + SOCK = mock { + send: -> + setpeername: -> + } + SOCK + + describe "shader", -> + it "handles simple shaders", -> + rt = COPILOT\eval_once '$shader"just simple content"' + assert.is.true rt\is_const! + shader = rt.result! + assert.is.equal "just simple content", shader.code + assert.is.same {}, shader.uniforms + assert.is.same {}, shader.bindings + assert.is.same {}, shader.deps + + it "interpolates strings", -> + rt = COPILOT\eval_once '$shader"just $"simple" content"' + assert.is.equal "just simple content", rt.result!.code + + it "interpolates uniforms", -> + rt = COPILOT\eval_once '$[1]shader"let val = $4;"' + assert.is.equal "struct Uniforms { + _1: f32, +}; +@group(1) @binding(0) var<uniform> u: Uniforms; + +let val = u._1;", rt.result!\compile 1 + + messages = rt.result!\get_binding_messages 1 + assert.is.equal 1, #messages + assert.is.same { + address: '/uniform/package::alv::main_1::u/_1', + types: 'f', + 4, + }, messages[1].content + + it "interpolates bindings", -> + rt = COPILOT\eval_once '$[4]shader"let s = $([5]sampler "linear" "repeat");"' + assert.is.equal "@group(2) @binding(1) var b_1: sampler; + +let s = b_1;", rt.result!\compile 2 + + messages = rt.result!\get_binding_messages 2 + assert.is.equal 1, #messages + assert.is.same { + address: '/binding/package::alv::main_4::b_1', + types: 's', + '/sampler/alv-main_5', + }, messages[1].content + + it "respects casted type", -> + rt = COPILOT\eval_once '$[1]shader"let val = $(cast "u32" 4);"' + assert.is.equal "struct Uniforms { + _1: u32, +}; +@group(1) @binding(0) var<uniform> u: Uniforms; + +let val = u._1;", rt.result!\compile 1 + + messages = rt.result!\get_binding_messages 1 + assert.is.equal 1, #messages + assert.is.same { + address: '/uniform/package::alv::main_1::u/_1', + types: 'f', + 4, + }, messages[1].content + + describe 'cast', -> + f32 = T["wgsl/f32"] + i32 = T["wgsl/i32"] + u32 = T["wgsl/u32"] + + it "infers types", -> + rt = COPILOT\eval_once '(cast 4)' + assert.is.equal (f32\mk_const 4), rt.result + + rt = COPILOT\eval_once '(cast [1 2])' + assert.is.equal T["wgsl/vec2<f32>"], rt\type! + + rt = COPILOT\eval_once '(cast [1 2 3 4 5])' + assert.is.equal T["wgsl/array<f32, 5>"], rt\type! + + rt = COPILOT\eval_once '(cast [[1 2] [3 4]])' + assert.is.equal T["wgsl/array<vec2<f32>, 2>"], rt\type! + + rt = COPILOT\eval_once '(cast [[1 2 3 4 5] [6 7 8 9 10]])' + assert.is.equal T["wgsl/array<array<f32, 5>, 2>"], rt\type! + + rt = COPILOT\eval_once '(cast [[1 2] [3 4] [5 6] [7 8] [9 10]])' + assert.is.equal T["wgsl/array<vec2<f32>, 5>"], rt\type! + + it "sets wgsl type", -> + rt = COPILOT\eval_once '(cast "i32" 4)' + assert.is.equal (i32\mk_const 4), rt.result + + it "overrides existing type", -> + rt = COPILOT\eval_once '(cast "u32" (cast "i32" 4))' + assert.is.equal (u32\mk_const 4), rt.result |
