blob: 4b09c50f5d7bd7b13fc799c5430303c727f9ad90 [file]
"""Single source for the rive_*_v1 wasm binding contract.
generate.py projects this table into the public C import header
(include/rive/wasm/rive_bindings_v1.h, the third party contract), the WAMR
NativeSymbol tables with drift checked prototypes (wasm_natives_gen.hpp), and
the node test stubs (luau_wasm/tests/vm_host_stubs.mjs). luau_wasm/test.sh
fails when the generated files drift from this source.
"""
def u32(name):
return {'kind': 'u32', 'name': name}
def i32(name):
return {'kind': 'i32', 'name': name}
def f32(name):
return {'kind': 'f32', 'name': name}
# Wide counters such as PCM frame clocks, which outgrow u32 within a day.
def f64(name):
return {'kind': 'f64', 'name': name}
def handle(tag, name=None):
return {'kind': 'handle', 'tag': tag, 'name': name or tag}
# Wasm validated pointer plus element count; arrives host side translated.
def buf(elem, name, count):
return {'kind': 'buf', 'elem': elem, 'name': name, 'count': count}
# Same validation but the host writes into it.
def mutbuf(elem, name, count):
return {'kind': 'mutbuf', 'elem': elem, 'name': name, 'count': count}
# Raw guest address the host validates and translates itself, for pointer
# pairs sharing one count.
def addr(elem, name):
return {'kind': 'addr', 'elem': elem, 'name': name}
def string(name, count):
return {'kind': 'str', 'name': name, 'count': count}
# Descriptor struct crossing as pointer plus byte count; u32 and f32 fields
# only, all four bytes, so the layout is identical module and host side. The
# byte count doubles as the struct version: hosts reject anything smaller
# than the fields they know.
def pod(name, fields):
return {'name': name, 'fields': fields}
def podref(podname, name):
return {'kind': 'pod', 'pod': podname, 'name': name}
PODS = [
pod('gpu_texture_desc', [
u32('width'),
u32('height'),
u32('depthOrArrayLayers'),
u32('format'),
u32('textureType'),
u32('renderTarget'),
u32('numMipmaps'),
u32('sampleCount'),
]),
pod('gpu_texture_upload', [
u32('bytesPerRow'),
u32('rowsPerImage'),
u32('mipLevel'),
u32('layer'),
u32('x'),
u32('y'),
u32('z'),
u32('width'),
u32('height'),
u32('depth'),
]),
pod('gpu_sampler_desc', [
u32('minFilter'),
u32('magFilter'),
u32('mipmapFilter'),
u32('wrapU'),
u32('wrapV'),
u32('wrapW'),
u32('compare'),
f32('minLod'),
f32('maxLod'),
u32('maxAnisotropy'),
]),
pod('gpu_texture_view_desc', [
u32('dimension'),
u32('aspect'),
u32('baseMipLevel'),
u32('mipCount'),
u32('baseLayer'),
u32('layerCount'),
]),
# Sizes slice the op's blob argument in field order; absent parts are 0.
pod('gpu_shader_module_desc', [
u32('language'),
u32('stage'),
u32('codeSize'),
u32('hlslSourceSize'),
u32('hlslEntryPointSize'),
u32('bindingMapSize'),
u32('glFixupSize'),
u32('shaderAssetId'),
]),
# Array element pods cross via buf() with a byte count; the count must be
# an exact multiple of the struct size.
pod('gpu_bind_group_layout_entry', [
u32('binding'),
u32('kind'),
u32('visibility'),
u32('hasDynamicOffset'),
u32('textureViewDim'),
u32('textureSampleType'),
u32('textureMultisampled'),
u32('minBindingSize'),
u32('nativeSlotVS'),
u32('nativeSlotFS'),
u32('nativeSlotCS'),
u32('samplerNonFiltering'),
]),
pod('gpu_bind_group_ubo', [
u32('slot'),
u32('buffer'),
u32('offset'),
u32('size'),
]),
pod('gpu_bind_group_texture', [
u32('slot'),
u32('view'),
]),
pod('gpu_bind_group_sampler', [
u32('slot'),
u32('sampler'),
]),
pod('gpu_color_target', [
u32('format'),
u32('blendEnabled'),
u32('srcColor'),
u32('dstColor'),
u32('colorOp'),
u32('srcAlpha'),
u32('dstAlpha'),
u32('alphaOp'),
u32('writeMask'),
]),
pod('gpu_vertex_buffer_layout', [
u32('stride'),
u32('stepMode'),
u32('attributeCount'),
]),
pod('gpu_vertex_attribute', [
u32('format'),
u32('offset'),
u32('shaderSlot'),
]),
pod('gpu_pass_color_attachment', [
u32('view'),
u32('resolveTarget'),
u32('loadOp'),
u32('storeOp'),
f32('clearR'),
f32('clearG'),
f32('clearB'),
f32('clearA'),
]),
pod('gpu_pass_desc', [
u32('colorCount'),
u32('depthView'),
u32('depthLoadOp'),
u32('depthStoreOp'),
f32('depthClearValue'),
u32('stencilLoadOp'),
u32('stencilStoreOp'),
u32('stencilClearValue'),
]),
# Sizes and counts slice the op's blob in field order: vertex entry
# string, fragment entry string, color targets, vertex buffer layouts,
# their attributes (concatenated in layout order), then bind group layout
# handles as u32s (0 = null group).
pod('gpu_pipeline_desc', [
u32('vertexModule'),
u32('fragmentModule'),
u32('vertexEntrySize'),
u32('fragmentEntrySize'),
u32('colorCount'),
u32('vertexBufferCount'),
u32('attributeCount'),
u32('bindGroupLayoutCount'),
u32('topology'),
u32('indexFormat'),
u32('cullMode'),
u32('winding'),
u32('depthFormat'),
u32('depthCompare'),
u32('depthWriteEnabled'),
u32('depthBias'),
f32('depthBiasSlopeScale'),
f32('depthBiasClamp'),
u32('stencilFrontCompare'),
u32('stencilFrontFailOp'),
u32('stencilFrontDepthFailOp'),
u32('stencilFrontPassOp'),
u32('stencilBackCompare'),
u32('stencilBackFailOp'),
u32('stencilBackDepthFailOp'),
u32('stencilBackPassOp'),
u32('stencilReadMask'),
u32('stencilWriteMask'),
u32('sampleCount'),
]),
]
def op(name, params=(), ret=None, stub=None, guard=None):
# guard: the host only carries the op when this macro is defined. Modules
# import what they call, so one that never calls it links anywhere.
return {'name': name, 'params': list(params), 'ret': ret, 'stub': stub,
'guard': guard}
def ns(module, short, ops):
return {'module': module, 'short': short, 'ops': ops}
NAMESPACES = [
ns('rive_rt_v1', 'rt', [
op('log', [i32('level'), string('message', 'length')], stub='hook'),
op('mark_needs_update', [handle('object')]),
# Raised by the module's fuel checks when the execution budget
# passes; the host prints the timeout and traps the op.
op('budget_exceeded', [u32('ms')]),
# Line probes a debug bake plants: function entry and exit keep a
# shadow call stack, and each line asks whether to stop. line
# returns nonzero when it did, so the module re-arms its budget.
op('debug_enter', [u32('func'), u32('line')]),
op('debug_line', [u32('line')], ret='u32', stub='zero'),
op('debug_leave'),
# os.date local time at an epoch: the host's UTC offset in seconds,
# signed, crossing as i32 bits; whether daylight saving is in
# effect; the zone name under the retrying length contract.
op('utc_offset', [f64('epochSeconds')], ret='u32', stub='zero'),
op('is_dst', [f64('epochSeconds')], ret='u32', stub='zero'),
op('zone_name', [f64('epochSeconds'),
mutbuf('char', 'buffer', 'capacity')],
ret='u32', stub='zero'),
]),
# Data binding tracer: numbers only; more property types, listeners, and
# the valueChanged callback channel land with the properties namespace.
ns('rive_data_v1', 'data', [
op('view_model', [handle('object')], ret='u32'),
# context:rootViewModel(): the data context's root instance.
op('root_view_model', [handle('object')], ret='u32'),
# context:globalViewModel(name): resolved through the object's file
# and data context like the Luau lane.
op('global_view_model', [handle('object'),
string('name', 'nameLength')], ret='u32'),
# Newline-joined names with the retrying length contract.
op('global_view_model_names', [
handle('object'),
mutbuf('char', 'buffer', 'capacity'),
], ret='u32'),
# context:dataContext() and the DataContext surface.
op('context', [handle('object')], ret='u32'),
op('context_parent', [handle('dataContext')], ret='u32'),
op('context_view_model', [handle('dataContext')], ret='u32'),
op('context_release', [handle('dataContext')]),
# The Data global: constructors over the file's view models. Empty
# template means a default instance.
op('has_view_model', [string('name', 'nameLength')], ret='u32'),
op('new_view_model', [string('name', 'nameLength'),
string('templateName', 'templateLength')],
ret='u32'),
op('vmi_release', [handle('vmi')]),
op('vmi_number', [handle('vmi'), string('name', 'length')],
ret='u32'),
op('vmi_boolean', [handle('vmi'), string('name', 'length')],
ret='u32'),
op('vmi_string', [handle('vmi'), string('name', 'length')],
ret='u32'),
op('vmi_trigger', [handle('vmi'), string('name', 'length')],
ret='u32'),
op('vmi_color', [handle('vmi'), string('name', 'length')],
ret='u32'),
op('vmi_view_model', [handle('vmi'), string('name', 'length')],
ret='u32'),
# Dynamic vm.name reads: kindOut[0] receives the DataPropertyWire
# kind, kindOut[1] the symbol list index value when that is the kind.
op('vmi_property', [handle('vmi'), string('name', 'length'),
mutbuf('uint32_t', 'kindOut', 'kindCount')],
ret='u32'),
# vm:instance(name?): a fresh instance of the same view model; empty
# name means the default instance.
op('vmi_instance', [handle('vmi'), string('name', 'nameLength')],
ret='u32'),
# vm:getIndex(); ~0u when the instance carries no symbol list index.
op('vmi_symbol_index', [handle('vmi')], ret='u32'),
# __eq: both handles wrap the same core instance.
op('vmi_equal', [handle('a'), handle('b')], ret='u32'),
# The property's .value: the referenced instance as a new vmi handle.
op('view_model_get', [handle('property')], ret='u32'),
op('vmi_list', [handle('vmi'), string('name', 'length')], ret='u32'),
op('vmi_enum', [handle('vmi'), string('name', 'length')], ret='u32'),
op('vmi_image', [handle('vmi'), string('name', 'length')], ret='u32'),
op('vmi_font', [handle('vmi'), string('name', 'length')], ret='u32'),
op('vmi_blob', [handle('vmi'), string('name', 'length')], ret='u32'),
# The property's image as a rive_image_v1 handle, resolved through
# the instance's embedded asset or the file registry; 0 when none.
op('image_get', [handle('property')], ret='u32'),
op('image_set', [handle('property'), handle('image')]),
# Fonts cross as opaque handles: the module has no font surface, so
# the value only round trips through get/set.
op('font_get', [handle('property')], ret='u32'),
op('font_set', [handle('property'), handle('font')]),
op('font_release', [handle('font')]),
# 1 when a blob resolves, including a zero byte runtime-set blob;
# distinguishes nil from empty since blob_get returns 0 for both.
op('blob_present', [handle('property')], ret='u32'),
op('blob_get', [handle('property'),
mutbuf('uint8_t', 'buffer', 'capacity')], ret='u32'),
op('blob_name', [handle('property'),
mutbuf('char', 'buffer', 'capacity')], ret='u32'),
op('blob_set', [handle('property'),
buf('uint8_t', 'bytes', 'byteCount')]),
op('blob_clear', [handle('property')]),
# Same retrying length contract as string_get; the value is the
# current key resolved through the property's data enum.
op('enum_get', [handle('property'),
mutbuf('char', 'buffer', 'capacity')], ret='u32'),
op('enum_set', [handle('property'), string('value', 'length')]),
# enum:values(): newline-joined keys with the retrying contract.
op('enum_values', [handle('property'),
mutbuf('char', 'buffer', 'capacity')], ret='u32'),
op('prop_release', [handle('property')]),
op('trigger_fire', [handle('property')]),
op('list_length', [handle('property')], ret='u32'),
op('list_push', [handle('property'), handle('vmi')]),
# Removed ends return the item's instance as a new vmi handle, 0 when
# empty.
op('list_pop', [handle('property')], ret='u32'),
op('list_shift', [handle('property')], ret='u32'),
op('list_clear', [handle('property')]),
# Indices are zero based on the wire; the module owns the script's
# one based convention and range errors.
op('list_swap', [handle('property'), u32('index1'), u32('index2')]),
op('list_insert', [handle('property'), handle('vmi'), u32('index')]),
op('list_remove', [handle('property'), handle('vmi')]),
op('list_remove_at', [handle('property'), u32('index')]),
op('list_remove_all_of', [handle('property'), handle('vmi')]),
# list[i]: the item's instance as a new vmi handle, 0 out of range.
op('list_get', [handle('property'), u32('index')], ret='u32'),
op('view_model_set', [handle('property'), handle('vmi')]),
op('color_get', [handle('property')], ret='u32'),
op('color_set', [handle('property'), u32('value')]),
op('number_get', [handle('property')], ret='f32'),
op('number_set', [handle('property'), f32('value')]),
op('boolean_get', [handle('property')], ret='u32'),
op('boolean_set', [handle('property'), u32('value')]),
# Returns the full byte length; copies what fits, callers retry with
# a larger buffer when truncated.
op('string_get', [handle('property'),
mutbuf('char', 'buffer', 'capacity')], ret='u32'),
op('string_set', [handle('property'), string('value', 'length')]),
# Value change delivery: the host calls the module's exported
# host_data_value_changed(token) whenever a watched property's value
# changes.
op('watch', [handle('property'), u32('token')]),
op('unwatch', [handle('property')]),
# The converted value produced inside host_obj_data_convert, written
# into the ScriptDataResult the pending callDataConvert points at;
# kind values are DataConvertWire's.
op('convert_result', [
u32('kind'),
f32('number'),
u32('booleanValue'),
u32('color'),
string('value', 'length'),
]),
]),
# Artboard inputs: the host owns the instance (and its state machine and
# bound view model); the module's Artboard userdata mirrors the Luau
# lane's surface over these ops.
ns('rive_artboard_v1', 'artboard', [
op('release', [handle('artboard')]),
op('advance', [handle('artboard'), f32('seconds')], ret='u32'),
op('draw', [handle('artboard'), handle('renderer')]),
# vmi 0 auto-creates the clone's instance like the Luau lane.
op('instance', [handle('artboard'), handle('vmi')], ret='u32'),
# The bound view model instance as a new vmi handle.
op('data', [handle('artboard')], ret='u32'),
op('width', [handle('artboard')], ret='f32'),
op('height', [handle('artboard')], ret='f32'),
op('set_width', [handle('artboard'), f32('value')]),
op('set_height', [handle('artboard'), f32('value')]),
op('frame_origin', [handle('artboard')], ret='u32'),
op('set_frame_origin', [handle('artboard'), u32('value')]),
# minX, minY, maxX, maxY.
op('bounds', [handle('artboard'), mutbuf('float', 'out', 'outCount')]),
# kind is ArtboardWire's pointer kind; returns the HitResult, 0 when
# the artboard has no state machine.
op('pointer_event', [
handle('artboard'),
u32('kind'),
u32('pointerId'),
f32('x'),
f32('y'),
], ret='u32'),
op('animation', [handle('artboard'), string('name', 'length')],
ret='u32'),
op('animation_release', [handle('animation')]),
op('animation_duration', [handle('animation')], ret='f32'),
op('animation_advance', [handle('animation'), f32('seconds')],
ret='u32'),
# mode is ArtboardWire's time mode (seconds/frames/percentage).
op('animation_set_time', [
handle('animation'),
f32('value'),
u32('mode'),
]),
op('node', [handle('artboard'), string('name', 'length')], ret='u32'),
op('node_release', [handle('node')]),
# x, y, rotation, scaleX, scaleY.
op('node_transform', [handle('node'),
mutbuf('float', 'out', 'outCount')]),
# field is ArtboardWire's node field; vector fields use v0, v1.
op('node_set', [handle('node'), u32('field'), f32('v0'), f32('v1')]),
op('node_world_transform', [handle('node'),
mutbuf('float', 'out', 'outCount')]),
op('node_set_world_transform', [
handle('node'),
buf('float', 'values', 'floatCount'),
]),
# node:decompose(mat): the world matrix crosses, the parent-space
# decomposition applies host side.
op('node_decompose', [handle('node'),
buf('float', 'values', 'floatCount')]),
# Retrying copy-out; ~0u means the node is not a Path.
op('node_path_verbs', [handle('node'),
mutbuf('uint8_t', 'out', 'outCount')],
ret='u32'),
op('node_path_points', [handle('node'),
mutbuf('float', 'out', 'outCount')],
ret='u32'),
# style, join, cap, blendMode, color, thicknessBits, featherBits;
# returns 0 when the node carries no shape paint.
op('node_paint', [handle('node'),
mutbuf('uint32_t', 'out', 'outCount')], ret='u32'),
# Retrying copy-out of the node's transform children as new node
# handles; handles mint only when they all fit.
op('node_children', [handle('node'),
mutbuf('uint32_t', 'out', 'outCount')],
ret='u32'),
# The parent as a new node handle, 0 when it is not a transform.
op('node_parent', [handle('node')], ret='u32'),
# The key custom properties are read by, ~0u when no property in the
# file carries that name. Resolve once, it scans the name table.
op('property_key', [handle('artboard'), string('name', 'length')],
ret='u32'),
# draw with a visitor: the host calls the module's host_draw_visit
# export for each drawable that has custom properties. The drawable
# handle is borrowed and goes stale when the export returns.
op('draw_visit', [handle('artboard'), handle('renderer')]),
# The host sets the color modulation per tagged drawable from its
# property of this key, with no callback into the module.
op('draw_modulated', [handle('artboard'), handle('renderer'),
u32('key')]),
op('drawable_draw', [handle('drawable'), handle('renderer')]),
# Returns the CustomPropertyKind plus one, 0 when absent; out[0]
# receives the value bits of a number, boolean or color.
op('drawable_value', [handle('drawable'), u32('key'),
mutbuf('uint32_t', 'out', 'outCount')],
ret='u32'),
# Retrying copy-out; ~0u means no string property by that key.
op('drawable_string', [handle('drawable'), u32('key'),
mutbuf('char', 'out', 'outCount')],
ret='u32'),
# Retrying copy-out of one kind byte, the name and a NUL per
# property.
op('drawable_properties', [handle('drawable'),
mutbuf('char', 'out', 'outCount')],
ret='u32', guard='WITH_RIVE_TOOLS'),
]),
# The Luau Audio surface: sources resolve from the object's file by
# asset name, sounds come back from the play ops. Frame clocks cross as
# f64 since the engine's run past u32; fades are frames, as the Luau
# lane passes them.
ns('rive_audio_v1', 'audio', [
op('source', [handle('object'), string('name', 'nameLength')],
ret='u32'),
op('source_release', [handle('source')]),
op('source_duration', [handle('source')], ret='f32'),
op('source_sample_rate', [handle('source')], ret='u32'),
op('source_channels', [handle('source')], ret='u32'),
# Each returns a new sound handle, 0 without an engine or source.
op('play', [handle('source')], ret='u32'),
op('play_at_time', [handle('source'), f32('seconds')], ret='u32'),
op('play_in_time', [handle('source'), f32('seconds')], ret='u32'),
op('play_at_frame', [handle('source'), f64('frame')], ret='u32'),
op('play_in_frame', [handle('source'), f64('frame')], ret='u32'),
op('time', ret='f32'),
op('time_frame', ret='f64'),
op('sample_rate', ret='u32'),
op('sound_release', [handle('sound')]),
op('sound_play', [handle('sound')]),
op('sound_pause', [handle('sound')]),
op('sound_resume', [handle('sound')]),
op('sound_stop', [handle('sound'), u32('fadeFrames')]),
op('sound_seek', [handle('sound'), f32('seconds')], ret='u32'),
op('sound_seek_frame', [handle('sound'), f64('frame')], ret='u32'),
op('sound_completed', [handle('sound')], ret='u32'),
op('sound_time', [handle('sound')], ret='f32'),
op('sound_time_frame', [handle('sound')], ret='f64'),
op('sound_volume', [handle('sound')], ret='f32'),
op('sound_set_volume', [handle('sound'), f32('value')]),
]),
ns('rive_path_v1', 'path', [
op('new', ret='u32'),
op('update', [
handle('path'),
buf('uint8_t', 'verbs', 'verbCount'),
buf('float', 'points', 'floatCount'),
u32('fillRule'),
]),
op('release', [handle('path')]),
# The geometry the script's effect update produced, appended into the
# RawPath the pending callPathEffectUpdate points at; the inverse of
# update's module-to-host crossing.
op('effect_result', [
buf('uint8_t', 'verbs', 'verbCount'),
buf('float', 'points', 'floatCount'),
]),
]),
# Path measurement over module geometry: a whole path measure or a
# contour iterator, both copying the geometry so the module's path can
# change underneath. Contour handles come from contour_next and answer
# the same ops as a path measure.
ns('rive_measure_v1', 'measure', [
op('path_new', [
buf('uint8_t', 'verbs', 'verbCount'),
buf('float', 'points', 'floatCount'),
], ret='u32'),
op('contours_new', [
buf('uint8_t', 'verbs', 'verbCount'),
buf('float', 'points', 'floatCount'),
], ret='u32'),
# The next contour of an iterator handle, 0 past the last.
op('contour_next', [handle('measure')], ret='u32'),
op('length', [handle('measure')], ret='f32'),
op('is_closed', [handle('measure')], ret='u32'),
# pos.x, pos.y, tan.x, tan.y
op('pos_tan', [handle('measure'), f32('distance'),
mutbuf('float', 'out', 'outCount')]),
op('warp', [handle('measure'), f32('x'), f32('y'),
mutbuf('float', 'out', 'outCount')]),
# Computes the segment host side and returns its verb count;
# extract_read copies it out (at most three points per verb).
op('extract', [handle('measure'), f32('startDistance'),
f32('endDistance'), u32('startWithMove')], ret='u32'),
op('extract_read', [
handle('measure'),
mutbuf('uint8_t', 'verbs', 'verbCount'),
mutbuf('float', 'points', 'floatCount'),
], ret='u32'),
op('release', [handle('measure')]),
]),
ns('rive_paint_v1', 'paint', [
op('new', ret='u32'),
op('release', [handle('paint')]),
op('style', [handle('paint'), u32('value')]),
op('color', [handle('paint'), u32('value')]),
op('thickness', [handle('paint'), f32('value')]),
op('join', [handle('paint'), u32('value')]),
op('cap', [handle('paint'), u32('value')]),
op('blend_mode', [handle('paint'), u32('value')]),
op('feather', [handle('paint'), f32('value')]),
op('shader', [handle('paint'), handle('shader')]),
]),
# 2D canvas: an offscreen Rive render target the script draws into with
# a frame's Renderer and composites with the image it mints. A zero size
# allocates nothing until resize, and a size requested before the device
# binds waits for it. Frames record through the deferred canvas host, so
# begin_frame answers 0 without one.
ns('rive_canvas_v1', 'canvas', [
op('new', [u32('width'), u32('height')], ret='u32'),
op('release', [handle('canvas')]),
op('width', [handle('canvas')], ret='u32'),
op('height', [handle('canvas')], ret='u32'),
# 1 on success; a zero dimension drops the backing.
op('resize', [handle('canvas'), u32('width'), u32('height')],
ret='u32'),
# Mints an image handle over the canvas's presentable RenderImage.
op('image', [handle('canvas')], ret='u32'),
# Mints the frame's renderer handle; clearColor is ARGB. end_frame
# releases it, so a stashed wrapper goes stale.
op('begin_frame', [handle('canvas'), u32('clearColor')], ret='u32'),
op('end_frame', [handle('canvas')]),
]),
# GPU tracer subset: one canvas, one clear pass. The full surface lands
# with descriptor PODs from ore_resource_commands.hpp.
ns('rive_gpu_v1', 'gpu', [
# Host GPU capability snapshot in context:features() declaration
# order, bools as 0/1. Returns values written; 0 means no ore
# context so the module answers conservative defaults, ~0u means
# the recording carries no ReplayCaps yet so the module raises
# like the Luau backend.
op('features', [mutbuf('uint32_t', 'out', 'outCount')], ret='u32'),
op('canvas_new', [u32('width'), u32('height')], ret='u32'),
op('canvas_release', [handle('canvas')]),
# Mints a view handle over the canvas's own color view; props receives
# width, height, format, sampleCount so module-side attachment
# validation sees real metadata.
op('canvas_color_view', [
handle('canvas'),
mutbuf('uint32_t', 'props', 'propCount'),
], ret='u32'),
# Mints an image handle over the canvas's presentable RenderImage,
# the canvas .image surface.
op('canvas_image', [handle('canvas')], ret='u32'),
# Recreates the host canvas at a new size and mints a view over the
# fresh color texture; props mirror canvas_color_view. The canvas
# handle itself stays valid.
op('canvas_resize', [
handle('canvas'),
u32('width'),
u32('height'),
mutbuf('uint32_t', 'props', 'propCount'),
], ret='u32'),
op('pass_begin', [
podref('gpu_pass_desc', 'desc'),
buf('rive_gpu_pass_color_attachment_v1', 'colors',
'colorByteCount'),
], ret='u32'),
op('pass_set_pipeline', [handle('pass'), handle('pipeline')]),
op('pass_set_vertex_buffer', [
handle('pass'),
u32('slot'),
handle('buffer'),
u32('offset'),
]),
op('pass_set_index_buffer', [
handle('pass'),
handle('buffer'),
u32('indexFormat'),
u32('offset'),
]),
op('pass_set_bind_group', [
handle('pass'),
u32('groupIndex'),
handle('bindGroup'),
buf('uint32_t', 'dynamicOffsets', 'dynamicOffsetByteCount'),
]),
op('pass_set_viewport', [
handle('pass'),
f32('x'),
f32('y'),
f32('width'),
f32('height'),
f32('minDepth'),
f32('maxDepth'),
]),
op('pass_set_scissor', [
handle('pass'),
u32('x'),
u32('y'),
u32('width'),
u32('height'),
]),
op('pass_set_stencil_reference', [handle('pass'), u32('ref')]),
op('pass_set_blend_color', [
handle('pass'),
f32('r'),
f32('g'),
f32('b'),
f32('a'),
]),
op('pass_draw', [
handle('pass'),
u32('vertexCount'),
u32('instanceCount'),
u32('firstVertex'),
u32('firstInstance'),
]),
op('pass_draw_indexed', [
handle('pass'),
u32('indexCount'),
u32('instanceCount'),
u32('firstIndex'),
i32('baseVertex'),
u32('firstInstance'),
]),
op('pass_finish', [handle('pass')]),
op('pass_release', [handle('pass')]),
# Recording-side Image:view(): the host records the wrap by resource
# id and the module gets a sampling view handle.
op('image_view', [
handle('image'),
u32('width'),
u32('height'),
], ret='u32'),
# ore BufferDesc fields; usage is the BufferUsage enum, dataCount 0
# means no initial contents.
op('buffer_new', [
u32('usage'),
u32('sizeInBytes'),
u32('immutable'),
buf('uint8_t', 'data', 'dataCount'),
], ret='u32'),
op('buffer_update', [
handle('buffer'),
u32('dstOffset'),
buf('uint8_t', 'data', 'dataCount'),
]),
op('buffer_release', [handle('buffer')]),
op('texture_new', [podref('gpu_texture_desc', 'desc')], ret='u32'),
op('texture_upload', [
handle('texture'),
podref('gpu_texture_upload', 'region'),
buf('uint8_t', 'data', 'dataCount'),
]),
op('texture_release', [handle('texture')]),
op('sampler_new', [podref('gpu_sampler_desc', 'desc')], ret='u32'),
op('sampler_release', [handle('sampler')]),
op('texture_view_new', [
handle('texture'),
podref('gpu_texture_view_desc', 'desc'),
], ret='u32'),
op('texture_view_release', [handle('view')]),
# Which RSTB variant the replay backend consumes; the module mirrors
# it so entry selection matches the host.
op('shader_target', ret='u32'),
# Copies the named ShaderAsset's RSTB container; returns the full
# length for the retrying copy-out convention, 0 when absent.
op('shader_asset_bytes', [
handle('object'),
string('name', 'nameLength'),
mutbuf('uint8_t', 'out', 'outCount'),
], ret='u32'),
# The asset's core id; re-decoded module-side assets lose it, and it
# rides the recorded shader module descriptor.
op('shader_asset_id', [
handle('object'),
string('name', 'nameLength'),
], ret='u32'),
op('shader_module_new', [
podref('gpu_shader_module_desc', 'desc'),
buf('uint8_t', 'blob', 'blobCount'),
], ret='u32'),
op('shader_module_release', [handle('shaderModule')]),
op('bind_group_layout_new', [
u32('groupIndex'),
buf('rive_gpu_bind_group_layout_entry_v1', 'entries',
'entryByteCount'),
], ret='u32'),
op('bind_group_layout_release', [handle('layout')]),
# Host derives the group's entries from the shader's binding map;
# dynamicUBOs lists @binding values whose UBOs take dynamic offsets.
op('bind_group_layout_from_shader', [
handle('shaderModule'),
u32('groupIndex'),
buf('uint32_t', 'dynamicUBOs', 'dynamicUBOCount'),
], ret='u32'),
op('bind_group_new', [
handle('layout'),
buf('rive_gpu_bind_group_ubo_v1', 'ubos', 'uboByteCount'),
buf('rive_gpu_bind_group_texture_v1', 'textures',
'textureByteCount'),
buf('rive_gpu_bind_group_sampler_v1', 'samplers',
'samplerByteCount'),
], ret='u32'),
op('bind_group_release', [handle('bindGroup')]),
op('pipeline_new', [
podref('gpu_pipeline_desc', 'desc'),
buf('uint8_t', 'blob', 'blobCount'),
], ret='u32'),
op('pipeline_release', [handle('pipeline')]),
]),
ns('rive_buffer_v1', 'buffer', [
# type and flags are the RenderBufferType/RenderBufferFlags enums.
op('new', [u32('bufferType'), u32('flags'), u32('sizeInBytes')],
ret='u32'),
op('update', [handle('buffer'),
buf('uint8_t', 'bytes', 'byteCount')]),
op('release', [handle('buffer')]),
]),
ns('rive_blob_v1', 'blob', [
# Resolves a non-empty BlobAsset by name from the object's file,
# the context:blob surface. Returns the full byte count; callers
# retry with a larger buffer when it exceeds capacity.
op('asset_bytes', [
handle('object'),
string('name', 'nameLength'),
mutbuf('uint8_t', 'out', 'outCount'),
], ret='u32'),
]),
ns('rive_image_v1', 'image', [
# Resolves an ImageAsset by name from the object's file, pinning its
# decoded RenderImage.
op('from_asset', [handle('object'), string('name', 'length')],
ret='u32'),
op('width', [handle('image')], ret='u32'),
op('height', [handle('image')], ret='u32'),
op('release', [handle('image')]),
# Starts a host-side async decode of encoded image bytes for
# context:decodeImage. Completion lands on a later advance through
# the module's host_image_decoded / host_image_decode_failed
# exports, keyed by the module-issued token. Returns 0 when the
# host build carries no decoders.
op('decode', [buf('uint8_t', 'bytes', 'byteCount'), u32('token')],
ret='u32', stub='hook'),
op('decode_cancel', [u32('token')]),
]),
ns('rive_shader_v1', 'shader', [
op('linear', [
f32('sx'),
f32('sy'),
f32('ex'),
f32('ey'),
addr('uint32_t', 'colors'),
addr('float', 'stops'),
u32('count'),
], ret='u32'),
op('radial', [
f32('cx'),
f32('cy'),
f32('radius'),
addr('uint32_t', 'colors'),
addr('float', 'stops'),
u32('count'),
], ret='u32'),
op('release', [handle('shader')]),
]),
ns('rive_renderer_v1', 'renderer', [
op('save', [handle('renderer')]),
op('restore', [handle('renderer')]),
op('transform', [
handle('renderer'),
f32('xx'),
f32('xy'),
f32('yx'),
f32('yy'),
f32('tx'),
f32('ty'),
]),
op('draw_path', [
handle('renderer'),
handle('path'),
handle('paint'),
]),
op('clip_path', [handle('renderer'), handle('path')]),
# color is packed ARGB; replace sets it instead of multiplying.
op('modulate_color', [handle('renderer'), u32('color'),
u32('replace')]),
# sampler is ImageSampler::asKey(); blend is the BlendMode enum.
op('draw_image', [
handle('renderer'),
handle('image'),
u32('sampler'),
u32('blend'),
f32('opacity'),
]),
# Counts derive from the buffer sizes host side; more than seven
# integer args would spill to the stack, which WAMR's generic native
# invocation drops.
op('draw_image_mesh', [
handle('renderer'),
handle('image'),
u32('sampler'),
handle('vertexBuffer'),
handle('uvBuffer'),
handle('indexBuffer'),
u32('blend'),
f32('opacity'),
]),
]),
]