| <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> |