From 261e2c21ce3056080e8a3dc88dd932406bd8ecdb Mon Sep 17 00:00:00 2001 From: Yong He Date: Thu, 7 Nov 2024 16:35:56 -0800 Subject: [PATCH] More demo updates. --- .vscode/settings.json | 57 +++++++++++++++++++++++++++++++++++++++++++ demos/circle.slang | 56 ++++++++++++++++++++++++++++++++---------- demos/demo-list.js | 4 +-- demos/ocean.slang | 39 ++++++++++++++++++----------- try-slang.js | 37 +++++++--------------------- ui.js | 56 ++++++++++++++++++++++-------------------- 6 files changed, 165 insertions(+), 84 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3c9af24 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,57 @@ +{ + "files.associations": { + "xstring": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "memory": "cpp", + "new": "cpp", + "ostream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "utility": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xtr1common": "cpp", + "xutility": "cpp" + } +} \ No newline at end of file diff --git a/demos/circle.slang b/demos/circle.slang index 57ed1aa..f0cd270 100644 --- a/demos/circle.slang +++ b/demos/circle.slang @@ -1,20 +1,50 @@ +// A simple shader ported from https://www.shadertoy.com/view/XdlSDs. +// Author: dynamite + import playground; +typealias vec2 = float2; +typealias vec3 = float3; +typealias vec4 = float4; + float4 imageMain(uint2 dispatchThreadID, int2 screenSize) { - float2 size = float2(screenSize.x, screenSize.y); - float2 center = size / 2.0; - - float2 pos = float2(dispatchThreadID.xy); - - float stripSize = screenSize.x / 40; + vec2 p = (dispatchThreadID.xy * 2 - screenSize.xy) / (float)screenSize.y; + float tau = float.getPi() * 2.0; + float a = atan2(p.x,p.y); + float r = length(p)*0.75; + vec2 uv = vec2(a/tau,r); + + float time = getTime() * 0.2; - const float time = getTime(); // from playgournd - float dist = distance(pos, center) + time; - float strip = dist / stripSize % 2.0; + //get the color + float xCol = (uv.x - time/3) * 3.0; + xCol = fmod(abs(xCol), 3.0f); + vec3 horColour = vec3(0.25, 0.25, 0.25); + + if (xCol < 1.0) + { + horColour.r += 1.0 - xCol; + horColour.g += xCol; + } + else if (xCol < 2.0) + { + xCol -= 1.0; + horColour.g += 1.0 - xCol; + horColour.b += xCol; + } + else + { + xCol -= 2.0; + horColour.b += 1.0 - xCol; + horColour.r += xCol; + } - if (strip < 1.0f) - return float4(1.0f, 0.0f, 0.0f, 1.0f); - else - return float4(0.0f, 1.0f, 1.0f, 1.0f); + // draw color beam + uv = (2.0 * uv) - 1.0; + float beamWidth = (0.7 + + 0.5 * cos(uv.x * 10.0 * tau * 0.15 * clamp(floor(5.0 + 10.0 * cos(time)), 0.0, 10.0))) + * abs(1.0 / (30.0 * uv.y)); + vec3 horBeam = vec3(beamWidth); + return vec4(((horBeam) * horColour), 1.0); } \ No newline at end of file diff --git a/demos/demo-list.js b/demos/demo-list.js index 04e3faf..1aaf86a 100644 --- a/demos/demo-list.js +++ b/demos/demo-list.js @@ -2,6 +2,6 @@ var demoList = [ { name: "Simple Print", url: "simple-print.slang" }, { name: "Simple Image", url: "simple-image.slang" }, { name: "-", url: "" }, - { name: "Circle", url: "circle.slang" }, - { name: "Ocean", url: "ocean.slang" } + { name: "ShaderToy: Circle", url: "circle.slang" }, + { name: "ShaderToy: Ocean", url: "ocean.slang" } ]; \ No newline at end of file diff --git a/demos/ocean.slang b/demos/ocean.slang index e163555..0a8be9a 100644 --- a/demos/ocean.slang +++ b/demos/ocean.slang @@ -1,3 +1,10 @@ +// The ocean shader from ShaderToys by afl_ext. +// https://www.shadertoy.com/view/MdXyzX + +// This shader is modified to use Slang's automatic differentiation feature +// to compute the normal of the wave by differentiating the `getwaves` function +// with regard to `position`. See `normal` function for how autodiff is used. + // afl_ext 2017-2024 // MIT License import playground; @@ -10,6 +17,8 @@ static const int ITERATIONS_NORMAL = 37; // waves iterations when calculating no // Calculates wave value and its derivative, // for the wave direction, position in space, wave frequency and time +[Differentiable] +[PreferCheckpoint] float2 wavedx(float2 position, float2 direction, float frequency, float timeshift) { float x = dot(direction, position) * frequency + timeshift; float wave = exp(sin(x) - 1.0); @@ -17,8 +26,10 @@ float2 wavedx(float2 position, float2 direction, float frequency, float timeshif return float2(wave, -dx); } -// Calculates waves by summing octaves of various waves with various parameters -float getwaves(float2 position, int iterations) { +// Calculates waves by summing octaves of various waves with various parameters. +// This function is a generic function that can be specialized by different number of iterations. +[Differentiable] +float getwaves(float2 position) { float wavePhaseShift = length(position) * 0.1; // this is to avoid every octave having exactly the same phase everywhere float iter = 0.0; // this will help generating well distributed wave directions float frequency = 1.0; // frequency of the wave, this will change every iteration @@ -26,12 +37,17 @@ float getwaves(float2 position, int iterations) { float weight = 1.0;// weight in final sum for the wave, this will change every iteration float sumOfValues = 0.0; // will store final sum of values float sumOfWeights = 0.0; // will store final sum of weights + [ForceUnroll] for(int i=0; i < iterations; i++) { // generate some wave direction that looks kind of random float2 p = float2(sin(iter), cos(iter)); // calculate wave data - float2 res = wavedx(position, p, frequency, getTime() * timeMultiplier + wavePhaseShift); + + // Since we don't need to propagate gradient through time and `getTime()` is a non-differentiable + // function, we need to use `no_diff` here to express our intention that we don't need gradients + // to propagate through the call. + float2 res = wavedx(position, p, frequency, no_diff getTime() * timeMultiplier + wavePhaseShift); // shift position around according to wave drag and derivative of the wave position += p * res.y * weight * DRAG_MULT; @@ -58,7 +74,7 @@ float raymarchwater(float3 camera, float3 start, float3 end, float depth) { float3 dir = normalize(end - start); for(int i=0; i < 64; i++) { // the height is from 0 to -depth - float height = getwaves(pos.xz, ITERATIONS_RAYMARCH) * depth - depth; + float height = getwaves(pos.xz) * depth - depth; // if the waves height almost nearly matches the ray height, assume its a hit and return the hit distance if(height + 0.01 > pos.y) { return distance(pos, camera); @@ -71,17 +87,12 @@ float raymarchwater(float3 camera, float3 start, float3 end, float depth) { return distance(start, camera); } -// Calculate normal at point by calculating the height at the pos and 2 additional points very close to pos +// Calculate normal at point by using autodiff to get the derivative of `getwave` function +// with regard to `position`. float3 normal(float2 pos, float e, float depth) { - float2 ex = float2(e, 0); - float H = getwaves(pos.xy, ITERATIONS_NORMAL) * depth; - float3 a = float3(pos.x, H, pos.y); - return normalize( - cross( - a - float3(pos.x - e, getwaves(pos.xy - ex.xy, ITERATIONS_NORMAL) * depth, pos.y), - a - float3(pos.x, getwaves(pos.xy + ex.yx, ITERATIONS_NORMAL) * depth, pos.y + e) - ) - ); + DifferentialPair diffPos = diffPair(pos); + bwd_diff(getwaves)(diffPos, 1.0); + return normalize(float3(-diffPos.d.x, 1.0, -diffPos.d.y)); } // Helper function generating a rotation matrix around the axis by the angle diff --git a/try-slang.js b/try-slang.js index 49c0cc7..bb2cfbc 100644 --- a/try-slang.js +++ b/try-slang.js @@ -16,36 +16,10 @@ var currentWindowSize = [300, 150]; const RENDER_MODE = SlangCompiler.RENDER_SHADER; const PRINT_MODE = SlangCompiler.PRINT_SHADER; const HIDDEN_MODE = SlangCompiler.NON_RUNNABLE_SHADER; +const defaultShaderURL = "circle.slang"; var currentMode = RENDER_MODE; -// TODO: Question? -// When we generate the shader code to wgsl, the uniform variable (float time) will have 16 bytes alignment, which is not shown in the slang -// code. So how the user can know the correct alignment of the uniform variable without using the slang reflection API or -// looking at the generated shader code? -const defaultShaderCode = ` -import playground; - -float4 imageMain(uint2 dispatchThreadID, int2 screenSize) -{ - float2 size = float2(screenSize.x, screenSize.y); - float2 center = size / 2.0; - - float2 pos = float2(dispatchThreadID.xy); - - float stripSize = screenSize.x / 40; - - const float time = getTime(); // from playgournd - float dist = distance(pos, center) + time; - float strip = dist / stripSize % 2.0; - - if (strip < 1.0f) - return float4(1.0f, 0.0f, 0.0f, 1.0f); - else - return float4(0.0f, 1.0f, 1.0f, 1.0f); -} -`; - async function webgpuInit() { const adapter = await navigator.gpu?.requestAdapter(); @@ -508,7 +482,14 @@ function runIfFullyInitialized() if (device) { - compileOrRun(); + if (monacoEditor.getValue() == "") + { + loadDemo(defaultShaderURL); + } + else + { + compileOrRun(); + } } } } diff --git a/ui.js b/ui.js index c4b7e0d..0cc1dea 100644 --- a/ui.js +++ b/ui.js @@ -137,34 +137,36 @@ function initializeModal() { }; } +function loadDemo(selectedDemoURL) { + if (selectedDemoURL != "") + { + // Is `selectedDemoURL` a relative path? + var finalURL; + if (!selectedDemoURL.startsWith("http")) + { + // If so, append the current origin to it. + // Combine the url to point to demos/${selectedDemoURL}. + finalURL = new URL("demos/" + selectedDemoURL, window.location.href); + } + else + { + finalURL = new URL(selectedDemoURL); + } + // Retrieve text from selectedDemoURL. + fetch(finalURL) + .then((response) => response.text()) + .then((data) => { + monacoEditor.setValue(data); + compileOrRun(); + }); + } +} + function handleDemoDropdown() { - const demoDropdown = document.getElementById("demo-dropdown"); - const selectInput = demoDropdown.querySelector(".dropdown-select"); + const selectInput = document.getElementById("demo-select"); selectInput.addEventListener("change", function () { - var selectedDemoURL = this.value; - if (selectedDemoURL != "") - { - // Is `selectedDemoURL` a relative path? - var finalURL; - if (!selectedDemoURL.startsWith("http")) - { - // If so, append the current origin to it. - // Combine the url to point to demos/${selectedDemoURL}. - finalURL = new URL("demos/" + selectedDemoURL, window.location.href); - } - else - { - finalURL = new URL(selectedDemoURL); - } - // Retrieve text from selectedDemoURL. - fetch(finalURL) - .then((response) => response.text()) - .then((data) => { - monacoEditor.setValue(data); - compileOrRun(); - }); - } + loadDemo(this.value); }); } @@ -206,7 +208,7 @@ function loadDemoList() } document.addEventListener("DOMContentLoaded", function () { - var initShaderCode = defaultShaderCode; + var initShaderCode = ""; const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { @@ -216,7 +218,7 @@ document.addEventListener("DOMContentLoaded", function () { }); } else { - loadEditor(false, "codeEditor", defaultShaderCode); + loadEditor(false, "codeEditor", initShaderCode); } loadEditor(true, "diagnostics", "Diagnostic Output"); loadEditor(true, "codeGen", "Generated Target Code");