| <canvas id="canvas" style="width:960; height:540" width=1920 height=1080></canvas> |
| <script src="rive.js"></script> |
| <script src="webgpu_player.js"></script> |
| <script> |
| async function loadRiveAsset(args) |
| { |
| const file = RiveLoadFile(await download(args.url)); |
| let artboard; |
| if (args.artboard) { |
| artboard = file.artboardNamed(args.artboard); |
| } else { |
| artboard = file.artboardDefault(); |
| } |
| let asset; |
| if (args.stateMachine) { |
| asset = artboard.stateMachineNamed(args.stateMachine); |
| } else if (args.animation) { |
| asset = artboard.animationNamed(args.animation); |
| } else { |
| asset = artboard.defaultStateMachine(); |
| } |
| asset.artboard = artboard; |
| asset.file = file; |
| return asset; |
| } |
| |
| const data = {}; |
| const assets = {}; |
| |
| function sleep(ms) |
| { |
| return new Promise(resolve => setTimeout(resolve, ms)); |
| } |
| |
| async function wgpuDataInitialize(wgpu, canvas, adapter, device) |
| { |
| data.device = device; |
| data.queue = device.queue; |
| data.width = canvas.width; |
| data.height = canvas.height; |
| data.wgpu = wgpu; |
| |
| // Rive has to be initialized before loading assets. |
| const invertY = false |
| RiveInitialize(data.device, data.queue, data.width, data.height, invertY, RIVE_PLS_TYPE_NONE); |
| |
| assets.car_demo = await loadRiveAsset({url:"car_demo.riv"}); |
| assets.cloud_icon = await loadRiveAsset({url:"cloud_icon.riv"}); |
| assets.coffee_loader = await loadRiveAsset({url:"coffee_loader.riv"}); |
| assets.documentation = await loadRiveAsset({url:"documentation.riv"}); |
| assets.fire_button = await loadRiveAsset({url:"fire_button.riv"}); |
| assets.lumberjackfinal = await loadRiveAsset({url:"lumberjackfinal.riv"}); |
| assets.mail_box = await loadRiveAsset({url:"mail_box.riv"}); |
| assets.new_file = await loadRiveAsset({url:"new_file.riv"}); |
| assets.poison_loader = await loadRiveAsset({url:"poison_loader.riv"}); |
| assets.popsicle_loader = await loadRiveAsset({url:"popsicle_loader.riv"}); |
| assets.radio_button_example = await loadRiveAsset({url:"radio_button_example.riv"}); |
| assets.avatar_demo = await loadRiveAsset({url:"avatar_demo.riv"}); |
| assets.stopwatch = await loadRiveAsset({url:"stopwatch.riv"}); |
| assets.vomune_bars = await loadRiveAsset({url:"volume_bars.riv", animation:"Example"}); |
| assets.travel_icons = await loadRiveAsset({url:"travel_icons.riv", artboard:"GPS", |
| animation:"Animation 1"}); |
| |
| // Update assets that have a number input from 1-100. |
| let lastPercentage = 0; |
| setInterval(function() { |
| // Go to 150 in order to pause at the end. |
| let percentage = (totalElapsed * 25) % 150; |
| if (percentage < lastPercentage) |
| percentage = 0; // Make sure we hit zero on our way around to reset the animation. |
| assets.coffee_loader.setNumber("numLoader", percentage); |
| assets.popsicle_loader.setNumber("percentage", percentage); |
| lastPercentage = percentage; |
| }, 8); |
| |
| // Simple cloud routine. |
| setInterval(async function() { |
| assets.cloud_icon.setBool("isHover", true); |
| await sleep(1500); |
| assets.cloud_icon.setBool("isPressed", true); |
| await sleep(200); |
| assets.cloud_icon.setBool("isPressed", false); |
| assets.cloud_icon.setBool("isHover", false); |
| }, 6700); |
| |
| let pressed = false; |
| setInterval(function() { |
| // Click the new_file button. |
| assets.new_file.pointerDown(75, 75); |
| assets.new_file.pointerUp(75, 75); |
| |
| // Toggle the radio_button_example and avatar_demo. |
| pressed = !pressed; |
| assets.radio_button_example.setBool("Pressed", pressed); |
| assets.avatar_demo.setBool("isPressed", pressed); |
| |
| // Fire the stopwatch. |
| assets.stopwatch.fireTrigger("Pressed"); |
| }, 5000); |
| |
| // Simple mailbox routine. |
| setInterval(async function() { |
| assets.mail_box.setBool("Mail_in/out", false); |
| await sleep(2000); |
| assets.mail_box.setBool("Mail_in/out", true); |
| await sleep(2000); |
| assets.mail_box.fireTrigger("Hover/Pressed"); |
| }, 6000); |
| |
| assets.radio_button_example.cell_size = 40; |
| } |
| |
| const fps = new function() { |
| this.frames = 0; |
| this.seconds = 0; |
| this.tick = function(elapsed) { |
| ++this.frames; |
| this.seconds += elapsed; |
| if (this.seconds >= 2) { |
| console.log((this.frames / this.seconds).toFixed(2) + " FPS"); |
| this.frames = 0; |
| this.seconds = 0; |
| } |
| }; |
| }; |
| |
| let lastTimestamp = 0; |
| let totalElapsed = 0; |
| |
| async function wgpuRender(args) |
| { |
| const COLS = 5; |
| const ROWS = Math.ceil(Object.keys(assets).length / COLS); |
| const CELL_SIZE = 128; |
| const CELL_SPACING = CELL_SIZE * 5/4; |
| |
| const targetTextureView = data.wgpu.getCurrentTexture().createView(); |
| const renderer = RiveBeginRendering(targetTextureView, RIVE_LOAD_ACTION_CLEAR, 0xff8030ff); |
| const elapsed = lastTimestamp ? (args.timestamp - lastTimestamp) / 1000 : 0; |
| lastTimestamp = args.timestamp; |
| |
| renderer.translate((data.width - CELL_SPACING * (COLS - 1)) / 2, |
| (data.height - CELL_SPACING * (ROWS - 1)) / 2); |
| renderer.save(); |
| let i = 0; |
| for (asset in assets) { |
| assets[asset].advanceAndApply(elapsed); |
| |
| renderer.save(); |
| const cell_size = assets[asset].cell_size || CELL_SIZE; |
| assets[asset].artboard.align(renderer, |
| [-cell_size/2, -cell_size/2, cell_size/2, cell_size/2], |
| RIVE_FIT_CONTAIN, |
| RIVE_ALIGNMENT_CENTER); |
| assets[asset].draw(renderer); |
| renderer.restore(); |
| |
| if (++i % COLS == 0) { |
| renderer.restore(); |
| renderer.translate(0, CELL_SPACING); |
| renderer.save(); |
| } else { |
| renderer.translate(CELL_SPACING, 0); |
| } |
| } |
| renderer.restore(); |
| |
| RiveFlushRendering(); |
| totalElapsed += elapsed; |
| fps.tick(elapsed); |
| } |
| |
| function download(url) |
| { |
| return new Promise(function (resolve, reject) { |
| let xhr = new XMLHttpRequest(); |
| xhr.open("GET", url); |
| xhr.responseType = "arraybuffer"; |
| xhr.onload = function () { |
| if (this.status >= 200 && this.status < 300) { |
| resolve(new Uint8Array(xhr.response)); |
| } else { |
| reject({ |
| status: this.status, |
| statusText: xhr.statusText |
| }); |
| } |
| }; |
| xhr.onerror = function () { |
| reject({ |
| status: this.status, |
| statusText: xhr.statusText |
| }); |
| }; |
| xhr.send(); |
| }); |
| } |
| |
| async function main() |
| { |
| const canvas = document.getElementById("canvas"); |
| const wgpu = canvas.getContext("webgpu"); |
| |
| const adapter = await navigator.gpu.requestAdapter(); |
| if (!adapter) { |
| throw new Error("No appropriate GPUAdapter found."); |
| } |
| |
| const device = await adapter.requestDevice(); |
| |
| const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); |
| wgpu.configure({ |
| device: device, |
| format: canvasFormat, |
| }); |
| |
| await wgpuDataInitialize(wgpu, canvas, adapter, device); |
| |
| const mainLoop = async () => { |
| await wgpuRender({timestamp:performance.now()}); |
| requestAnimationFrame(mainLoop); |
| }; |
| requestAnimationFrame(mainLoop); |
| } |
| |
| if (Module.calledRun) { |
| main(); |
| } else { |
| Module.onRuntimeInitialized = main; |
| } |
| </script> |