diff --git a/README.md b/README.md index 3a57f8e5..5e0913ed 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,8 @@ You can have a Vertex + Fragment shaders without a Compute shader, and also a Co ```js // import the `Points` class -import Points, { - RenderPass -} from '../src/absulit.points.module.js'; +import Points from '../src/absulit.points.module.js'; +import RenderPass from '../src/RenderPass.js'; // reference the canvas in the constructor const points = new Points('gl-canvas'); @@ -455,7 +454,54 @@ Common uses: > - name - name this property/variable will have inside the shader > - size - number of items it will allocate > - structName - You can use one of the default structs/types like `f32` , `i32` , `u32` , but if you use a more complex one you have to pair it properly with structSize. If it's a custom `struct` it has to be declared in the shader or it will throw an error. -> - structSize - if the `struct` you reference in `structName` has 4 properties then you have to add `4` . If it's only a f32 then here you should place `1` . +> - structSize - if the `struct` you reference in `structName` has 4 properties then you have to add `4` . If it's only a f32 then here you should place `1` . If the `struct` has vectors in it, the size of the vector counts: + +```rust +// structSize: 1 +f32 +u32 +i32 + +// structSize: 2 +vec2 +vec2 +vec2 +vec2f +vec2i +vec2u + +// structSize: 3 +vec3 +vec3 +vec3 +vec3f +vec3i +vec3u + +// structSize: 4 +vec4 +vec4 +vec4 +vec4f +vec4i +vec4u + +// CUSTOM STRUCTS + +// structSize: 3 +struct Variables { + value1: vec2f, + value2: f32, +} + +// structSize: 5 +struct Variables2 { + value1: vec3f, + value2: i32, + value3: u32, +} + +``` --- @@ -498,6 +544,13 @@ let b = value_noise_data[0]; variables.isCreated = 1; ``` +You can also add a default type instead of a custom struct in `structName`: + +```js +points.addStorage('myVar', 1, 'f32', 1); // size is 1 +points.addStorage('myVar2', 1, 'vec2f', 2); // size is 2 +``` + ## StorageMap - addStorageMap Creates a Storage in the same way as a `addStorage` does, except it can be set with data from the start of the application. @@ -592,6 +645,70 @@ async function init() { let rgbaWebcam = textureSampleBaseClampToEdge(webcam, feedbackSampler, fract(uv)); ``` +## Audio - addAudio + +You can load audio and use its data for visualization. + +```js +// index.js +let audio = points.addAudio('myAudio', './../../audio/cognitive_dissonance.mp3', volume, loop, false); +``` + + +With the `myAudio` name, a `Sound` type named `myAudio` is created. In the future it will have more information but now it only has the `data` property. `data` is an `array`, but it's not completely filled with data, it's only filled up to `params.myAudioLength`, (`myAudio` used as prefix for each different audio) and then each of these values has a max of 256, so if you want something like a percentage, you have to divide the value at a certain index between 256 +```rust +let audioX = audio.data[ u32(uvr.x * params.audioLength)] / 256; +``` + + +--- + +> **Note:** The `points.addAudio` method returns a `new Audio` reference, you are responsible to start and stop the audio from the JavaScript side, if you require to start and stop a sound by creating a call from the shaders, please check the `Events - addEventListener` section + +--- + +## Events - addEventListener + +An event is an asynchronous message that can be send from the WGSL shaders to the JavaScript code. It works in a very similar way as to listen for a click in JavaScript or a screen resize, you call a `addEventListener`, but the parameters and its use change a bit. In the code below, the first parameter is the name of the event you , the event is fired by **you**, so this name currently has no predefined names like 'click', or 'mouse over', you have to define them and dispatch them. + +The second parameter is the data (if any) that you will send from the WGSL shaders, this data is returnes as an array, so you have to acces each item by its index. + +The last parameter is the amount of items in this array you are expecting. All items are `f32`. + +```js +// as in examples\events1\index.js +points.addEventListener( + 'right_blink', // name of the event (and name of a storage) + data => { // data returned after the event and callback + console.log('---- Right Circle'); + }, + 4 // size of the data to return +); +``` + +To fire an event you first need to declare the listener. The name used in the listener is also used as storage buffer that you can manipulate to dispatch the event. + +The event has the following structure: + +```rust +// as in src\core\defaultStructs.js +struct Event { + updated: u32, + data: array +} +``` + +To actully fire an event you have to do as follows: + +```rust +right_blink.data[0] = 2; // some data +right_blink.data[1] = 2; // some data +right_blink.updated = 1; // update this property to something diff than `0` +``` + +By just simply changing the value of `updated` to a non zero, the library knows this event has been updated, and will dispatch the event immediately in JavaScript, so the `console.log` will print the text in the JavaScript Console. `updated` will be set as zero in the next frame, and if not updated the library then knows it doesn't have to do anything. + + ## Layers - addLayers A layer is basically a Storage but pre-made with the exact same dimension of the canvas, this for potentially create multi-layered effects that require a type of temporary storage and swap values between them. All items are `vec4` diff --git a/audio/cognitive_dissonance b/audio/cognitive_dissonance new file mode 100644 index 00000000..a685efa7 --- /dev/null +++ b/audio/cognitive_dissonance @@ -0,0 +1,3 @@ +"Cognitive Dissonance" Kevin MacLeod (incompetech.com) +Licensed under Creative Commons: By Attribution 4.0 License +http://creativecommons.org/licenses/by/4.0/ \ No newline at end of file diff --git a/audio/cognitive_dissonance.mp3 b/audio/cognitive_dissonance.mp3 new file mode 100644 index 00000000..d289e175 Binary files /dev/null and b/audio/cognitive_dissonance.mp3 differ diff --git a/audio/generative_audio_test.ogg b/audio/generative_audio_test.ogg new file mode 100644 index 00000000..1a585a69 Binary files /dev/null and b/audio/generative_audio_test.ogg differ diff --git a/examples/audio1/compute.js b/examples/audio1/compute.js new file mode 100644 index 00000000..00dd5ed7 --- /dev/null +++ b/examples/audio1/compute.js @@ -0,0 +1,13 @@ +const compute = /*wgsl*/` + +@compute @workgroup_size(8,8,1) +fn main( + @builtin(global_invocation_id) GlobalId: vec3, + @builtin(workgroup_id) WorkGroupID: vec3, + @builtin(local_invocation_id) LocalInvocationID: vec3 +) { + let time = params.time; +} +`; + +export default compute; diff --git a/examples/audio1/frag.js b/examples/audio1/frag.js new file mode 100644 index 00000000..e2cc6423 --- /dev/null +++ b/examples/audio1/frag.js @@ -0,0 +1,43 @@ +import { fnusin } from '../../src/core/animation.js'; +import { snoise } from './../../src/core/noise2d.js'; +import { sdfCircle } from './../../src/core/sdf.js'; +import { WHITE, RED, layer } from './../../src/core/color.js'; +import { audioAverage, audioAverageSegments } from '../../src/core/audio.js'; + +const frag = /*wgsl*/` + +${fnusin} +${snoise} +${sdfCircle} +${layer} +${audioAverage} +${audioAverageSegments} +${WHITE} +${RED} + +@fragment +fn main( + @location(0) color: vec4, + @location(1) uv: vec2, + @location(2) ratio: vec2, // relation between params.screenWidth and params.screenHeight + @location(3) uvr: vec2, // uv with aspect ratio corrected + @location(4) mouse: vec2, + @builtin(position) position: vec4 +) -> @location(0) vec4 { + + let audioX = audio.data[ u32(uvr.x * params.audioLength)] / 256; + + if(params.mouseClick == 1.){ + click_event.updated = 1; + } + + + var c = vec4f(); + c.r = audioX; + c.a = 1.; + + return c; +} +`; + +export default frag; diff --git a/examples/audio1/index.js b/examples/audio1/index.js new file mode 100644 index 00000000..fd04ba3c --- /dev/null +++ b/examples/audio1/index.js @@ -0,0 +1,40 @@ +import vert from './vert.js'; +import compute from './compute.js'; +import frag from './frag.js'; +import Points from './../../src/absulit.points.module.js'; + +let audio = null; + +const base = { + vert, + compute, + frag, + /** + * + * @param {Points} points + */ + init: async points => { + let volume = 1; + let loop = true; + // audio = points.addAudio('audio', './../../audio/generative_audio_test.ogg', volume, loop); + audio = points.addAudio('audio', './../../audio/cognitive_dissonance.mp3', volume, loop, false); + points.addEventListener('click_event', data => { + audio.play(); + }, 2); + // points.addAudio('audio', 'https://mdn.github.io/voice-change-o-matic/audio/concert-crowd.ogg', volume, loop); + points.addStorage('result', 10, 'f32', 1); + }, + /** + * + * @param {Points} points + */ + update: points => { + }, + + remove: _ => { + audio.pause(); + audio.remove(); + } +} + +export default base; \ No newline at end of file diff --git a/examples/audio1/vert.js b/examples/audio1/vert.js new file mode 100644 index 00000000..fdf316e3 --- /dev/null +++ b/examples/audio1/vert.js @@ -0,0 +1,15 @@ +const vert = /*wgsl*/` + +@vertex +fn main( + @location(0) position: vec4, + @location(1) color: vec4, + @location(2) uv: vec2, + @builtin(vertex_index) vertexIndex: u32 +) -> Fragment { + + return defaultVertexBody(position, color, uv); +} +`; + +export default vert; diff --git a/examples/audio2/compute.js b/examples/audio2/compute.js new file mode 100644 index 00000000..00dd5ed7 --- /dev/null +++ b/examples/audio2/compute.js @@ -0,0 +1,13 @@ +const compute = /*wgsl*/` + +@compute @workgroup_size(8,8,1) +fn main( + @builtin(global_invocation_id) GlobalId: vec3, + @builtin(workgroup_id) WorkGroupID: vec3, + @builtin(local_invocation_id) LocalInvocationID: vec3 +) { + let time = params.time; +} +`; + +export default compute; diff --git a/examples/audio2/frag.js b/examples/audio2/frag.js new file mode 100644 index 00000000..fedb157a --- /dev/null +++ b/examples/audio2/frag.js @@ -0,0 +1,71 @@ +import { fnusin } from '../../src/core/animation.js'; +import { snoise } from '../../src/core/noise2d.js'; +import { sdfCircle } from '../../src/core/sdf.js'; +import { WHITE, RED, GREEN, YELLOW, layer } from '../../src/core/color.js'; +import { audioAverage, audioAverageSegments } from '../../src/core/audio.js'; +import { texturePosition } from '../../src/core/image.js'; + +const frag = /*wgsl*/` + +${fnusin} +${snoise} +${sdfCircle} +${layer} +${audioAverage} +${audioAverageSegments} +${WHITE} +${RED} +${GREEN} +${YELLOW} +${texturePosition} + +@fragment +fn main( + @location(0) color: vec4, + @location(1) uv: vec2, + @location(2) ratio: vec2, // relation between params.screenWidth and params.screenHeight + @location(3) uvr: vec2, // uv with aspect ratio corrected + @location(4) mouse: vec2, + @builtin(position) position: vec4 +) -> @location(0) vec4 { + + if(params.mouseClick == 1.){ + click_event.updated = 1; + } + + // let audioAverage = audioAverage(audio); + // let audioAverageSegments = audioAverageSegments(2); + + let n = snoise(uvr / params.sliderA + params.time); + let feedbackColor = texturePosition(feedbackTexture, imageSampler, vec2(), uvr * vec2f(1, 1.01), true); + + let segmentNum = 4; + let subSegmentLength = i32(params.audioLength) / segmentNum; + + for (var index = 0; index < segmentNum ; index++) { + var audioAverage = 0.; + for (var index2 = 0; index2 < subSegmentLength; index2++) { + let audioIndex = index2 * index; + + let audioValue = audio.data[audioIndex] / 256; + audioAverage += audioValue; + } + result[index] = audioAverage / f32(subSegmentLength); + } + + + let circle1 = sdfCircle(vec2(.5), result[0] * .4, .0, uvr) * WHITE; + let circle2 = sdfCircle(vec2(.5), result[1] * .4, .0, uvr) * GREEN; + let circle3 = sdfCircle(vec2(.5), result[2] * .4, .0, uvr) * YELLOW; + let circle4 = sdfCircle(vec2(.5), result[3] * .4, .0, uvr) * RED; + + + // return c; + // return layer(circle2 * WHITE, c + circle); + // return c + circle - circle2; + + return layer(feedbackColor * .9, layer(circle1, layer(circle2, layer(circle3, circle4)))); +} +`; + +export default frag; diff --git a/examples/audio2/index.js b/examples/audio2/index.js new file mode 100644 index 00000000..19479d50 --- /dev/null +++ b/examples/audio2/index.js @@ -0,0 +1,52 @@ +import vert from './vert.js'; +import compute from './compute.js'; +import frag from './frag.js'; +import Points from '../../src/absulit.points.module.js'; +import RenderPasses from './../../src/RenderPasses.js'; + +let audio = null; + +const base = { + vert, + compute, + frag, + /** + * + * @param {Points} points + */ + init: async points => { + let volume = 1; + let loop = true; + // let audio = points.addAudio('audio', './../../audio/generative_audio_test.ogg', volume, loop); + audio = points.addAudio('audio', './../../audio/cognitive_dissonance.mp3', volume, loop, false); + // audio = points.addAudio('audio', 'https://mdn.github.io/voice-change-o-matic/audio/concert-crowd.ogg', volume, loop); + + points.addEventListener('click_event', data => { + audio.play(); + }, 2); + + + points.addStorage('result', 10, 'f32', 1); + + // RenderPasses.filmgrain(points); + // RenderPasses.bloom(points, .1); + // RenderPasses.lensDistortion(points, .5, .01); + // RenderPasses.blur(points, 100, 100, .1,0); + + points.addSampler('imageSampler', null); + points.addTexture2d('feedbackTexture', true); + }, + /** + * + * @param {Points} points + */ + update: points => { + }, + + remove: _ => { + audio.pause(); + audio.remove(); + } +} + +export default base; \ No newline at end of file diff --git a/examples/audio2/vert.js b/examples/audio2/vert.js new file mode 100644 index 00000000..fdf316e3 --- /dev/null +++ b/examples/audio2/vert.js @@ -0,0 +1,15 @@ +const vert = /*wgsl*/` + +@vertex +fn main( + @location(0) position: vec4, + @location(1) color: vec4, + @location(2) uv: vec2, + @builtin(vertex_index) vertexIndex: u32 +) -> Fragment { + + return defaultVertexBody(position, color, uv); +} +`; + +export default vert; diff --git a/examples/base/index.js b/examples/base/index.js index ffcce147..ed8b581b 100644 --- a/examples/base/index.js +++ b/examples/base/index.js @@ -1,11 +1,16 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; +import Points from './../../src/absulit.points.module.js'; const base = { vert, compute, frag, + /** + * + * @param {Points} points + */ init: async points => { }, diff --git a/examples/basic.html b/examples/basic.html index 42536901..9d73b7b5 100644 --- a/examples/basic.html +++ b/examples/basic.html @@ -15,7 +15,8 @@ // import the `Points` class - import Points, {RenderPass} from '../src/absulit.points.module.js'; + import Points from '../src/absulit.points.module.js'; + import RenderPass from '../src/RenderPass.js'; import { fnusin } from '../src/core/animation.js'; // reference the canvas in the constructor diff --git a/examples/bloom1/frag.js b/examples/bloom1/frag.js index 847ca23c..2061ab2f 100644 --- a/examples/bloom1/frag.js +++ b/examples/bloom1/frag.js @@ -31,7 +31,7 @@ fn main( ) -> @location(0) vec4 { let startPosition = vec2(0.,0.); - let rgbaImage = texturePosition(image, feedbackSampler, startPosition, uvr / params.sliderA, true); //* .998046; + let rgbaImage = texturePosition(image, imageSampler, startPosition, uvr / params.sliderA, true); //* .998046; let input = rgbaImage.r; let bloomVal = bloom(input, 2, params.sliderB); diff --git a/examples/bloom1/index.js b/examples/bloom1/index.js index d63c94a0..9535f519 100644 --- a/examples/bloom1/index.js +++ b/examples/bloom1/index.js @@ -5,7 +5,12 @@ const bloom1 = { vert, frag, init: async points => { - points.addSampler('feedbackSampler'); + let descriptor = { + addressModeU: 'repeat', + addressModeV: 'repeat', + } + + points.addSampler('imageSampler', descriptor); // await points.addTextureImage('image', './../img/carmen_lyra_423x643.jpg'); // await points.addTextureImage('image', './../img/old_king_600x600.jpg'); await points.addTextureImage('image', './../img/absulit_800x800.jpg'); diff --git a/examples/circleblur/index.js b/examples/circleblur/index.js index d5088067..b85a0188 100644 --- a/examples/circleblur/index.js +++ b/examples/circleblur/index.js @@ -1,14 +1,14 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; +import RenderPass from './../../src/RenderPass.js'; const circleblur = { renderPasses: [ new RenderPass(null, null, compute, 800, 800, 1), new RenderPass(vert, frag, null, 8, 1, 1) ], init: async points => { - points.addSampler('feedbackSampler'); + points.addSampler('feedbackSampler', null); points.addTexture2d('feedbackTexture', true); points.addBindingTexture('outputTex', 'computeTexture'); }, diff --git a/examples/data1/index.js b/examples/data1/index.js index b5c4277a..7d8a8e33 100644 --- a/examples/data1/index.js +++ b/examples/data1/index.js @@ -3,6 +3,8 @@ import compute from './compute.js'; +let read = false; + const data1 = { compute, init: async points => { @@ -25,15 +27,19 @@ const data1 = { points.addStorageMap('secondMatrix', secondMatrix, 'Matrix'); - let resultMatrixBufferSize = Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1]); + let resultMatrixBufferSize = 2 + firstMatrix[0] * secondMatrix[1]; + console.log(resultMatrixBufferSize); points.addStorage('resultMatrix', 1, 'Matrix', resultMatrixBufferSize, true); }, update: async points => { }, read: async points => { - let result = await points.readStorage('resultMatrix'); - console.log(result); + if(!read){ + let result = await points.readStorage('resultMatrix'); + console.log(result); + read = true; + } } } diff --git a/examples/dithering1/index.js b/examples/dithering1/index.js index 3c359414..91f0bd11 100644 --- a/examples/dithering1/index.js +++ b/examples/dithering1/index.js @@ -4,7 +4,11 @@ const dithering1 = { vert, frag, init: async points => { - points.addSampler('feedbackSampler', null); + let descriptor = { + addressModeU: 'repeat', + addressModeV: 'repeat', + } + points.addSampler('feedbackSampler', descriptor); // await points.addTextureImage('image', './../img/carmen_lyra_423x643.jpg'); // await points.addTextureImage('image', './../img/old_king_600x600.jpg'); await points.addTextureImage('image', './../img/absulit_800x800.jpg'); diff --git a/examples/dithering2/index.js b/examples/dithering2/index.js index d4169534..f523f38c 100644 --- a/examples/dithering2/index.js +++ b/examples/dithering2/index.js @@ -4,7 +4,11 @@ const dithering2 = { vert, frag, init: async points => { - points.addSampler('feedbackSampler', null); + let descriptor = { + addressModeU: 'repeat', + addressModeV: 'repeat', + } + points.addSampler('feedbackSampler', descriptor); // await points.addTextureImage('image', './../img/carmen_lyra_423x643.jpg'); // await points.addTextureImage('image', './../img/old_king_600x600.jpg'); await points.addTextureImage('image', './../img/absulit_800x800.jpg'); diff --git a/examples/dithering3_1/frag.js b/examples/dithering3_1/frag.js index d6c48df1..84faa5fc 100644 --- a/examples/dithering3_1/frag.js +++ b/examples/dithering3_1/frag.js @@ -54,7 +54,7 @@ fn main( //let imageUV = (uv / f + vec2(0, .549 ) ) * vec2(1,-1 * dimsRatio) * ratio.y / params.sliderA; //var point = textureSample(computeTexture, imageSampler, imageUV); //* .998046; - var point = texturePosition(computeTexture, imageSampler, vec2(0.), uv / params.sliderA, false); //* .998046; + var point = texturePosition(computeTexture, imageSampler, vec2(0.), uv / params.sliderA, true); //* .998046; return point; } diff --git a/examples/dithering3_1/index.js b/examples/dithering3_1/index.js index 9486b077..f0066260 100644 --- a/examples/dithering3_1/index.js +++ b/examples/dithering3_1/index.js @@ -1,13 +1,17 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { ShaderType } from '../../src/absulit.points.module.js'; +import ShaderType from '../../src/absulit.points.module.js'; const dithering3 = { vert, compute, frag, init: async points => { - points.addSampler('imageSampler', null); + let descriptor = { + addressModeU: 'repeat', + addressModeV: 'repeat', + } + points.addSampler('imageSampler', descriptor); // await points.addTextureImage('image', './../img/carmen_lyra_423x643.jpg'); // await points.addTextureImage('image', './../img/old_king_600x600.jpg'); // await points.addTextureWebcam('image'); diff --git a/examples/dithering3_2/index.js b/examples/dithering3_2/index.js index f16e3349..8a318d3b 100644 --- a/examples/dithering3_2/index.js +++ b/examples/dithering3_2/index.js @@ -1,13 +1,18 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { RenderPass, ShaderType } from '../../src/absulit.points.module.js'; +import RenderPass from './../../src/RenderPass.js'; +import ShaderType from './../../src/ShaderType.js'; const dithering3 = { renderPasses: [ new RenderPass(vert, frag, compute, 800, 800) ], init: async points => { - points.addSampler('imageSampler', null); + let descriptor = { + addressModeU: 'repeat', + addressModeV: 'repeat', + } + points.addSampler('imageSampler', descriptor); // await points.addTextureImage('image', './../img/carmen_lyra_423x643.jpg'); // await points.addTextureImage('image', './../img/old_king_800x00.jpg'); // await points.addTextureWebcam('image'); diff --git a/examples/dithering4/index.js b/examples/dithering4/index.js index cf35d8eb..6c608259 100644 --- a/examples/dithering4/index.js +++ b/examples/dithering4/index.js @@ -4,7 +4,11 @@ const dithering4 = { vert, frag, init: async points => { - points.addSampler('feedbackSampler', null); + let descriptor = { + addressModeU: 'clamp-to-edge', + addressModeV: 'clamp-to-edge', + } + points.addSampler('feedbackSampler', descriptor); // await points.addTextureImage('image', './../img/carmen_lyra_423x643.jpg'); // await points.addTextureImage('image', './../img/old_king_600x600.jpg'); await points.addTextureImage('image', './../img/absulit_800x800.jpg'); diff --git a/examples/events1/compute.js b/examples/events1/compute.js new file mode 100644 index 00000000..99fed441 --- /dev/null +++ b/examples/events1/compute.js @@ -0,0 +1,19 @@ +const compute = /*wgsl*/` + +struct Variable{ + init: f32, + circleRadius:f32, + circlePosition:vec2 +} + +@compute @workgroup_size(8,8,1) +fn main( + @builtin(global_invocation_id) GlobalId: vec3, + @builtin(workgroup_id) WorkGroupID: vec3, + @builtin(local_invocation_id) LocalInvocationID: vec3 +) { + let time = params.time; +} +`; + +export default compute; diff --git a/examples/events1/frag.js b/examples/events1/frag.js new file mode 100644 index 00000000..3fec0c20 --- /dev/null +++ b/examples/events1/frag.js @@ -0,0 +1,48 @@ +import { sdfCircle } from '../../src/core/sdf.js'; +import { WHITE, BLUE, GREEN, RED, YELLOW } from '../../src/core/color.js'; + +const frag = /*wgsl*/` + +${sdfCircle} +${WHITE + RED + GREEN + BLUE + YELLOW} + +@fragment +fn main( + @location(0) color: vec4, + @location(1) uv: vec2, + @location(2) ratio: vec2, // relation between params.screenWidth and params.screenHeight + @location(3) uvr: vec2, // uv with aspect ratio corrected + @location(4) mouse: vec2, + @builtin(position) position: vec4 + ) -> @location(0) vec4 { + + // < ------------ TWO PULSATING CIRCLES + var circle1Radius = .01; + var circle2Radius = .01; + + if(params.time % 1 == 0){ + circle1Radius = .1; + left_blink.data[0] = 1; + left_blink.data[1] = 1; + left_blink.updated = 1; + } + + if(params.time % 2 == 0){ + circle2Radius = .1; + right_blink.data[0] = 2; + right_blink.data[1] = 2; + right_blink.updated = 1; + } + + let circleValue1 = sdfCircle(vec2f(.2,.5), circle1Radius, 0., uvr); + let circleValue2 = sdfCircle(vec2f(.8,.5), circle2Radius, 0., uvr); + + var circle1Color = circleValue1 * WHITE; + var circle2Color = circleValue2 * WHITE; + // > ------------ TWO PULSATING CIRCLES + + return circle1Color + circle2Color; +} +`; + +export default frag; diff --git a/examples/events1/index.js b/examples/events1/index.js new file mode 100644 index 00000000..f9b3bc5b --- /dev/null +++ b/examples/events1/index.js @@ -0,0 +1,48 @@ +import vert from './vert.js'; +import compute from './compute.js'; +import frag from './frag.js'; +import Points from '../../src/absulit.points.module.js'; + +const base = { + vert, + compute, + frag, + /** + * + * @param {Points} points + */ + init: async points => { + // points.addStorage('event', 1, 'array', 4, true); + + points.addEventListener('left_blink', data => { + console.log('---- Left Circle'); + }, 4); + + points.addEventListener( + 'right_blink', // name of the event (and name of a storage) + data => { // data returned after the event and callback + console.log('---- Right Circle'); + }, + 4 // size of the data to return + ); + }, + /** + * + * @param {Points} points + */ + update: points => { + + }, + /** + * + * @param {Points} points + */ + read: async points => { + // let event = await points.readStorage('event'); + // console.clear(); + // console.log(event); + // console.log(event[0], event[1], event[2]); + } +} + +export default base; diff --git a/examples/events1/vert.js b/examples/events1/vert.js new file mode 100644 index 00000000..49d42a97 --- /dev/null +++ b/examples/events1/vert.js @@ -0,0 +1,21 @@ +const vert = /*wgsl*/` + +struct Variable{ + init: f32, + circleRadius:f32, + circlePosition:vec2 +} + +@vertex +fn main( + @location(0) position: vec4, + @location(1) color: vec4, + @location(2) uv: vec2, + @builtin(vertex_index) vertexIndex: u32 +) -> Fragment { + + return defaultVertexBody(position, color, uv); +} +`; + +export default vert; diff --git a/examples/imagescale1/index.js b/examples/imagescale1/index.js index e7bf9cc9..8d792527 100644 --- a/examples/imagescale1/index.js +++ b/examples/imagescale1/index.js @@ -4,7 +4,16 @@ const imagescale1 = { vert, frag, init: async points => { - points.addSampler('feedbackSampler', null); + let descriptor = { + addressModeU: 'repeat', + addressModeV: 'clamp-to-edge', + magFilter: 'nearest', + minFilter: 'nearest', + mipmapFilter: 'nearest', + // maxAnisotropy: 10, + // compare: 'always', + } + points.addSampler('feedbackSampler', descriptor); await points.addTextureImage('image1', './../img/gratia_800x800.jpg'); await points.addTextureImage('image2', './../img/old_king_600x600.jpg'); await points.addTextureImage('image3', './../img/unnamed_horror_100x100.png'); @@ -14,4 +23,15 @@ const imagescale1 = { } } -export default imagescale1; \ No newline at end of file +export default imagescale1; + +// enum GPUCompareFunction { +// "never", +// "less", +// "equal", +// "less-equal", +// "greater", +// "not-equal", +// "greater-equal", +// "always", +// }; \ No newline at end of file diff --git a/examples/main.js b/examples/main.js index 05404347..30c95d3c 100644 --- a/examples/main.js +++ b/examples/main.js @@ -1,6 +1,8 @@ 'use strict'; import * as dat from './../src/vendor/datgui/dat.gui.module.js'; -import Points, { RenderPass, ShaderType } from '../src/absulit.points.module.js'; +import Points from '../src/absulit.points.module.js'; +import ShaderType from '../src/ShaderType.js'; +import RenderPass from '../src/RenderPass.js'; /***************/ const stats = new Stats(); @@ -56,6 +58,8 @@ gui.add(isFitWindowData, 'isFitWindow').name('Fit Window').onChange(value => { const shaderProjects = [ { name: 'Base', path: './base/index.js' }, + { name: 'Audio 1', path: './audio1/index.js' }, + { name: 'Audio 2', path: './audio2/index.js' }, { name: 'Bloom1', path: './bloom1/index.js' }, { name: 'Circle Blur', path: './circleblur/index.js' }, { name: 'Data 1', path: './data1/index.js' }, @@ -65,6 +69,7 @@ const shaderProjects = [ { name: 'Dithering 3 - 1', path: './dithering3_1/index.js' }, { name: 'Dithering 3 - 2', path: './dithering3_2/index.js' }, { name: 'Dithering 4', path: './dithering4/index.js' }, + { name: 'Events 1', path: './events1/index.js' }, { name: 'Image Scale 1', path: './imagescale1/index.js' }, { name: 'Image Texture 1', path: './imagetexture1/index.js' }, { name: 'Image Texture 2', path: './imagetexture2/index.js' }, @@ -102,6 +107,7 @@ async function loadShaderByIndex(index) { localStorage.setItem('selected-shader', index); console.clear(); let shaderPath = shaderProjects[index].path; + shaders?.remove?.(); shaders = (await import(shaderPath)).default; await init(); } @@ -189,11 +195,9 @@ async function init() { hasVertexAndFragmentShader && (points.fitWindow = isFitWindow); update(); - - shaders.read && await shaders.read(points); } -function update() { +async function update() { stats.begin(); // code here @@ -203,8 +207,9 @@ function update() { points.updateUniform('sliderC', sliders.c); shaders.update(points); - points.update(); + await points.update(); + await shaders.read?.(points); // stats.end(); diff --git a/examples/random1/index.js b/examples/random1/index.js index e1c88b91..4e86b489 100644 --- a/examples/random1/index.js +++ b/examples/random1/index.js @@ -1,7 +1,7 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { ShaderType } from './../../src/absulit.points.module.js'; +import ShaderType from './../../src/absulit.points.module.js'; const random1 = { vert, compute, diff --git a/examples/random2/index.js b/examples/random2/index.js index e9ffd1e5..1ea9cf22 100644 --- a/examples/random2/index.js +++ b/examples/random2/index.js @@ -1,7 +1,10 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; +import RenderPass from './../../src/RenderPass.js'; + +let data = []; + const random2 = { renderPasses: [ new RenderPass(vert, frag, compute, 800, 800) @@ -9,11 +12,11 @@ const random2 = { init: async points => { points.addUniform('randNumber', 0); points.addUniform('randNumber2', 0); - let data = []; + // let data = []; for (let k = 0; k < 800*800; k++) { data.push(Math.random()); } - points.addStorageMap('rands', [0,0], 'array'); + points.addStorageMap('rands', data, 'array'); points.addSampler('feedbackSampler'); points.addTexture2d('feedbackTexture', true); points.addBindingTexture('outputTex', 'computeTexture'); @@ -21,11 +24,11 @@ const random2 = { update: points => { points.updateUniform('randNumber', Math.random()); points.updateUniform('randNumber2', Math.random()); - let data = []; + // data = []; for (let k = 0; k < 800*800; k++) { - data.push(Math.random()); + data[k] = Math.random(); } - points.updateStorageMap('rands', data); + // points.updateStorageMap('rands', data); } } diff --git a/examples/random3/index.js b/examples/random3/index.js index 9d7cb854..450b7fe1 100644 --- a/examples/random3/index.js +++ b/examples/random3/index.js @@ -1,7 +1,7 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; +import RenderPass from '../../src/RenderPass.js'; const random3 = { diff --git a/examples/renderpasses1/index.js b/examples/renderpasses1/index.js index 7b68689d..c400b5d4 100644 --- a/examples/renderpasses1/index.js +++ b/examples/renderpasses1/index.js @@ -4,7 +4,7 @@ import frag1 from './renderpass1/frag.js'; import vert2 from './renderpass2/vert.js'; import frag2 from './renderpass2/frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; +import RenderPass from '../../src/RenderPass.js'; const renderpasses1 = { /** diff --git a/examples/renderpasses2/index.js b/examples/renderpasses2/index.js index db7403e1..91bb2c9e 100644 --- a/examples/renderpasses2/index.js +++ b/examples/renderpasses2/index.js @@ -1,8 +1,8 @@ import vert1 from './renderpass1/vert.js'; import frag1 from './renderpass1/frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; -import { RenderPasses } from "../../src/RenderPasses.js"; +import RenderPass from '../../src/RenderPass.js'; +import RenderPasses from './../../src/RenderPasses.js'; const renderpasses1 = { /** diff --git a/examples/shapes1/index.js b/examples/shapes1/index.js index b25f8730..d9a82a1b 100644 --- a/examples/shapes1/index.js +++ b/examples/shapes1/index.js @@ -1,7 +1,7 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; +import RenderPass from '../../src/RenderPass.js'; const shapes1 = { renderPasses: [ new RenderPass(vert, frag, compute, 128, 1, 1) diff --git a/examples/shapes2/index.js b/examples/shapes2/index.js index ea29331b..87c4d8a2 100644 --- a/examples/shapes2/index.js +++ b/examples/shapes2/index.js @@ -1,7 +1,7 @@ import vert from './vert.js'; import compute from './compute.js'; import frag from './frag.js'; -import { RenderPass } from '../../src/absulit.points.module.js'; +import RenderPass from '../../src/RenderPass.js'; const shapes2 = { renderPasses: [ new RenderPass(vert, frag, compute, 800, 800, 1) diff --git a/src/RenderPass.js b/src/RenderPass.js new file mode 100644 index 00000000..00355867 --- /dev/null +++ b/src/RenderPass.js @@ -0,0 +1,124 @@ +'use strict'; + +export default class RenderPass { + /** + * A collection of Vertex, Compute and Fragment shaders that represent a RenderPass. + * This is useful for PostProcessing. + * @param {String} vertexShader WGSL Vertex Shader in a String. + * @param {String} fragmentShader WGSL Fragment Shader in a String. + * @param {String} computeShader WGSL Compute Shader in a String. + */ + constructor(vertexShader, fragmentShader, computeShader, workgroupCountX, workgroupCountY, workgroupCountZ) { + this._vertexShader = vertexShader; + this._computeShader = computeShader; + this._fragmentShader = fragmentShader; + + this._computePipeline = null; + this._renderPipeline = null; + + this._computeBindGroup = null; + this._uniformBindGroup = null; + + this._internal = false; + + this._compiledShaders = { + vertex: '', + compute: '', + fragment: '', + }; + + this._hasComputeShader = !!this._computeShader; + this._hasVertexShader = !!this._vertexShader; + this._hasFragmentShader = !!this._fragmentShader; + + this._hasVertexAndFragmentShader = this._hasVertexShader && this._hasFragmentShader; + + this._workgroupCountX = workgroupCountX || 8; + this._workgroupCountY = workgroupCountY || 8; + this._workgroupCountZ = workgroupCountZ || 1; + } + + get internal() { + return this._internal; + } + + set internal(value) { + this._internal = value; + } + + get vertexShader() { + return this._vertexShader; + } + + get computeShader() { + return this._computeShader; + } + + get fragmentShader() { + return this._fragmentShader; + } + + set computePipeline(value) { + this._computePipeline = value; + } + + get computePipeline() { + return this._computePipeline; + } + + set renderPipeline(value) { + this._renderPipeline = value; + } + + get renderPipeline() { + return this._renderPipeline; + } + + set computeBindGroup(value) { + this._computeBindGroup = value; + } + + get computeBindGroup() { + return this._computeBindGroup; + } + + set uniformBindGroup(value) { + this._uniformBindGroup = value; + } + + get uniformBindGroup() { + return this._uniformBindGroup; + } + + get compiledShaders() { + return this._compiledShaders; + } + + get hasComputeShader() { + return this._hasComputeShader; + } + + get hasVertexShader() { + return this._hasVertexShader; + } + + get hasFragmentShader() { + return this._hasFragmentShader; + } + + get hasVertexAndFragmentShader() { + return this._hasVertexAndFragmentShader; + } + + get workgroupCountX() { + return this._workgroupCountX; + } + + get workgroupCountY() { + return this._workgroupCountY; + } + + get workgroupCountZ() { + return this._workgroupCountZ; + } +} diff --git a/src/RenderPasses.js b/src/RenderPasses.js index b5f5599a..517d1188 100644 --- a/src/RenderPasses.js +++ b/src/RenderPasses.js @@ -8,12 +8,13 @@ import filmgrain from './core/RenderPasses/filmgrain/index.js'; import bloom from './core/RenderPasses/bloom/index.js'; import blur from './core/RenderPasses/blur/index.js'; import waves from './core/RenderPasses/waves/index.js'; -import Points, { RenderPass } from './absulit.points.module.js'; +import Points from './absulit.points.module.js'; +import RenderPass from './RenderPass.js'; /** * List of predefined Render Passes for Post Processing. */ -export class RenderPasses { +export default class RenderPasses { static COLOR = 1; static GRAYSCALE = 2; static CHROMATIC_ABERRATION = 3; diff --git a/src/ShaderType.js b/src/ShaderType.js new file mode 100644 index 00000000..c9a2c257 --- /dev/null +++ b/src/ShaderType.js @@ -0,0 +1,7 @@ +'use strict'; + +export default class ShaderType { + static VERTEX = 1; + static COMPUTE = 2; + static FRAGMENT = 3; +} diff --git a/src/UniformKeys.js b/src/UniformKeys.js new file mode 100644 index 00000000..51f8f5e1 --- /dev/null +++ b/src/UniformKeys.js @@ -0,0 +1,15 @@ +'use strict'; + +export default class UniformKeys { + static TIME = 'time'; + static EPOCH = 'epoch'; + static SCREEN_WIDTH = 'screenWidth'; + static SCREEN_HEIGHT = 'screenHeight'; + static MOUSE_X = 'mouseX'; + static MOUSE_Y = 'mouseY'; + static MOUSE_CLICK = 'mouseClick'; + static MOUSE_DOWN = 'mouseDown'; + static MOUSE_WHEEL = 'mouseWheel'; + static MOUSE_DELTA_X = 'mouseDeltaX'; + static MOUSE_DELTA_Y = 'mouseDeltaY'; +} \ No newline at end of file diff --git a/src/VertexBufferInfo.js b/src/VertexBufferInfo.js new file mode 100644 index 00000000..ada13c67 --- /dev/null +++ b/src/VertexBufferInfo.js @@ -0,0 +1,39 @@ +'use strict'; + +export default class VertexBufferInfo { + /** + * Along with the vertexArray it calculates some info like offsets required for the pipeline. + * @param {Float32Array} vertexArray array with vertex, color and uv data + * @param {Number} triangleDataLength how many items does a triangle row has in vertexArray + * @param {Number} vertexOffset index where the vertex data starts in a row of `triangleDataLength` items + * @param {Number} colorOffset index where the color data starts in a row of `triangleDataLength` items + * @param {Number} uvOffset index where the uv data starts in a row of `triangleDataLength` items + */ + constructor(vertexArray, triangleDataLength = 10, vertexOffset = 0, colorOffset = 4, uvOffset = 8) { + this._vertexSize = vertexArray.BYTES_PER_ELEMENT * triangleDataLength; // Byte size of ONE triangle data (vertex, color, uv). (one row) + this._vertexOffset = vertexArray.BYTES_PER_ELEMENT * vertexOffset; + this._colorOffset = vertexArray.BYTES_PER_ELEMENT * colorOffset; // Byte offset of triangle vertex color attribute. + this._uvOffset = vertexArray.BYTES_PER_ELEMENT * uvOffset; + this._vertexCount = vertexArray.byteLength / this._vertexSize; + } + + get vertexSize() { + return this._vertexSize; + } + + get vertexOffset() { + return this._vertexOffset; + } + + get colorOffset() { + return this._colorOffset; + } + + get uvOffset() { + return this._uvOffset; + } + + get vertexCount() { + return this._vertexCount; + } +} diff --git a/src/absulit.points.module.js b/src/absulit.points.module.js index 744f04d3..b0f115bd 100644 --- a/src/absulit.points.module.js +++ b/src/absulit.points.module.js @@ -1,247 +1,122 @@ 'use strict'; +import UniformKeys from './UniformKeys.js'; +import VertexBufferInfo from './VertexBufferInfo.js'; +import ShaderType from './ShaderType.js'; import Coordinate from './coordinate.js'; import RGBAColor from './color.js'; import defaultStructs from './core/defaultStructs.js'; import { defaultVertexBody } from './core/defaultFunctions.js'; -export class ShaderType { - static VERTEX = 1; - static COMPUTE = 2; - static FRAGMENT = 3; -} - -class UniformKeys { - static TIME = 'time'; - static EPOCH = 'epoch'; - static SCREEN_WIDTH = 'screenWidth'; - static SCREEN_HEIGHT = 'screenHeight'; - static MOUSE_X = 'mouseX'; - static MOUSE_Y = 'mouseY'; - static MOUSE_CLICK = 'mouseClick'; - static MOUSE_DOWN = 'mouseDown'; - static MOUSE_WHEEL = 'mouseWheel'; - static MOUSE_DELTA_X = 'mouseDeltaX'; - static MOUSE_DELTA_Y = 'mouseDeltaY'; -} - -export class RenderPass { - /** - * A collection of Vertex, Compute and Fragment shaders that represent a RenderPass. - * This is useful for PostProcessing. - * @param {String} vertexShader WGSL Vertex Shader in a String. - * @param {String} fragmentShader WGSL Fragment Shader in a String. - * @param {String} computeShader WGSL Compute Shader in a String. - */ - constructor(vertexShader, fragmentShader, computeShader, workgroupCountX, workgroupCountY, workgroupCountZ) { - this._vertexShader = vertexShader; - this._computeShader = computeShader; - this._fragmentShader = fragmentShader; - - this._computePipeline = null; - this._renderPipeline = null; - - this._computeBindGroup = null; - this._uniformBindGroup = null; - - this._internal = false; - - this._compiledShaders = { - vertex: '', - compute: '', - fragment: '', - } - - this._hasComputeShader = !!this._computeShader; - this._hasVertexShader = !!this._vertexShader; - this._hasFragmentShader = !!this._fragmentShader; - - this._hasVertexAndFragmentShader = this._hasVertexShader && this._hasFragmentShader; - - this._workgroupCountX = workgroupCountX || 8; - this._workgroupCountY = workgroupCountY || 8; - this._workgroupCountZ = workgroupCountZ || 1; - } - - get internal() { - return this._internal; - } - - set internal(value) { - this._internal = value; - } - - get vertexShader() { - return this._vertexShader; - } - - get computeShader() { - return this._computeShader; - } - - get fragmentShader() { - return this._fragmentShader; - } - - set computePipeline(value) { - this._computePipeline = value; - } - - get computePipeline() { - return this._computePipeline; - } - - set renderPipeline(value) { - this._renderPipeline = value; - } - - get renderPipeline() { - return this._renderPipeline; - } - - set computeBindGroup(value) { - this._computeBindGroup = value; - } - - get computeBindGroup() { - return this._computeBindGroup; - } - - set uniformBindGroup(value) { - this._uniformBindGroup = value; - } - - get uniformBindGroup() { - return this._uniformBindGroup; - } - - get compiledShaders() { - return this._compiledShaders; - } - - get hasComputeShader() { - return this._hasComputeShader; - } - - get hasVertexShader() { - return this._hasVertexShader; - } - - get hasFragmentShader() { - return this._hasFragmentShader; - } - - get hasVertexAndFragmentShader() { - return this._hasVertexAndFragmentShader; - } - - get workgroupCountX() { - return this._workgroupCountX; - } - - get workgroupCountY() { - return this._workgroupCountY; - } - - get workgroupCountZ() { - return this._workgroupCountZ; - } -} - -export class VertexBufferInfo { - /** - * Along with the vertexArray it calculates some info like offsets required for the pipeline. - * @param {Float32Array} vertexArray array with vertex, color and uv data - * @param {Number} triangleDataLength how many items does a triangle row has in vertexArray - * @param {Number} vertexOffset index where the vertex data starts in a row of `triangleDataLength` items - * @param {Number} colorOffset index where the color data starts in a row of `triangleDataLength` items - * @param {Number} uvOffset index where the uv data starts in a row of `triangleDataLength` items - */ - constructor(vertexArray, triangleDataLength = 10, vertexOffset = 0, colorOffset = 4, uvOffset = 8) { - this._vertexSize = vertexArray.BYTES_PER_ELEMENT * triangleDataLength; // Byte size of ONE triangle data (vertex, color, uv). (one row) - this._vertexOffset = vertexArray.BYTES_PER_ELEMENT * vertexOffset; - this._colorOffset = vertexArray.BYTES_PER_ELEMENT * colorOffset; // Byte offset of triangle vertex color attribute. - this._uvOffset = vertexArray.BYTES_PER_ELEMENT * uvOffset; - this._vertexCount = vertexArray.byteLength / this._vertexSize; - } - - get vertexSize() { - return this._vertexSize - } - - get vertexOffset() { - return this._vertexOffset; - } - - get colorOffset() { - return this._colorOffset; - } - - get uvOffset() { - return this._uvOffset; - } - - get vertexCount() { - return this._vertexCount; - } -} - export default class Points { constructor(canvasId) { + /** @private */ this._canvasId = canvasId; - this._canvas = null; + /** @private */ + this._canvas = document.getElementById(this._canvasId); + /** @private */ this._device = null; + /** @private */ this._context = null; + /** @private */ this._presentationFormat = null; + /** @private */ this._renderPasses = null; + /** @private */ this._postRenderPasses = []; + /** @private */ this._vertexBufferInfo = null; + /** @private */ this._buffer = null; + /** @private */ this._internal = false; + /** @private */ this._presentationSize = null; + /** @private */ this._depthTexture = null; + /** @private */ this._commandEncoder = null; + /** @private */ this._vertexArray = []; + /** @private */ this._numColumns = 1; + /** @private */ this._numRows = 1; + /** @private */ this._commandsFinished = []; + /** @private */ this._renderPassDescriptor = null; + /** @private */ this._uniforms = []; + /** @private */ this._storage = []; + /** @private */ this._readStorage = []; + /** @private */ this._samplers = []; + /** @private */ this._textures2d = []; + /** @private */ this._texturesExternal = []; + /** @private */ this._texturesStorage2d = []; + /** @private */ this._bindingTextures = []; + /** @private */ this._layers = []; - this._canvas = document.getElementById(this._canvasId); + /** @private */ + this._originalCanvasWidth = null; + /** @private */ + this._originalCanvasHeigth = null; + + /** @private */ this._time = 0; + /** @private */ this._epoch = 0; + /** @private */ this._mouseX = 0; + /** @private */ this._mouseY = 0; + /** @private */ this._mouseDown = false; + /** @private */ this._mouseClick = false; + /** @private */ this._mouseWheel = false; + /** @private */ this._mouseDeltaX = 0; + /** @private */ this._mouseDeltaY = 0; + /** @private */ + this._fullscreen = false; + /** @private */ + this._fitWindow = false; + /** @private */ + this._lastFitWindow = false; + + // audio + /** @private */ + this._sounds = []; + + /** @private */ + this._events = new Map(); + /** @private */ + this._events_ids = 0; + if (this._canvasId) { this._canvas.addEventListener('click', e => { this._mouseClick = true; }); - this._canvas.addEventListener('mousemove', e => { - this._mouseX = e.clientX; - this._mouseY = e.clientY; - }); + this._canvas.addEventListener('mousemove', this._onMouseMove); this._canvas.addEventListener('mousedown', e => { this._mouseDown = true; }); @@ -270,14 +145,10 @@ export default class Points { }); } - this._fullscreen = false; - this._fitWindow = false; - this._lastFitWindow = false; - // _readStorage should only be read once - this._readStorageCopied = false; } + /** @private */ _resizeCanvasToFitWindow = () => { if (this._fitWindow) { this._canvas.width = window.innerWidth; @@ -286,12 +157,14 @@ export default class Points { } } + /** @private */ _resizeCanvasToDefault = () => { this._canvas.width = this._originalCanvasWidth; this._canvas.height = this._originalCanvasHeigth; this._setScreenSize(); } + /** @private */ _setScreenSize = () => { this._presentationSize = [ this._canvas.clientWidth, @@ -318,6 +191,7 @@ export default class Points { }); } + /** @private */ _onMouseMove = e => { this._mouseX = e.clientX; this._mouseY = e.clientY; @@ -356,6 +230,20 @@ export default class Points { variable.value = value; } + /** + * + * @param {Array} arr + */ + updateUniforms(arr) { + arr.forEach(uniform => { + const variable = this._uniforms.find(v => v.name === uniform.name); + if (!variable) { + throw '`updateUniform()` can\'t be called without first `addUniform()`.'; + } + variable.value = uniform.value; + }) + } + /** * Creates a persistent memory buffer across every frame call. * @param {string} name Name that the Storage will have in the shader @@ -386,13 +274,13 @@ export default class Points { if (read) { let storageItem = { name: name, - size: structSize + size: structSize * size } this._readStorage.push(storageItem); } } - addStorageMap(name, arrayData, structName, shaderType) { + addStorageMap(name, arrayData, structName, read, shaderType) { if (this._nameExists(this._storage, name)) { return; } @@ -403,8 +291,17 @@ export default class Points { shaderType: shaderType, array: arrayData, buffer: null, + read: read, internal: this._internal }); + + if (read) { + let storageItem = { + name: name, + size: arrayData.length, + } + this._readStorage.push(storageItem); + } } updateStorageMap(name, arrayData) { @@ -416,14 +313,16 @@ export default class Points { } async readStorage(name) { - // TODO: if read true add flag in addStorage let storageItem = this._readStorage.find(storageItem => storageItem.name === name); let arrayBuffer = null; + let arrayBufferCopy = null; if (storageItem) { - await storageItem.buffer.mapAsync(GPUMapMode.READ) + await storageItem.buffer.mapAsync(GPUMapMode.READ); arrayBuffer = storageItem.buffer.getMappedRange(); + arrayBufferCopy = new Float32Array(arrayBuffer.slice(0)); + storageItem.buffer.unmap(); } - return new Float32Array(arrayBuffer); + return arrayBufferCopy; } addLayers(numLayers, shaderType) { @@ -441,6 +340,7 @@ export default class Points { } } + /** @private */ _nameExists(arrayOfObjects, name) { return arrayOfObjects.some(obj => obj.name == name); } @@ -578,7 +478,61 @@ export default class Points { }); } - // + addAudio(name, path, volume, loop, autoplay) { + const audio = new Audio(path); + audio.volume = volume; + audio.autoplay = autoplay; + audio.loop = loop; + + const sound = { + name: name, + path: path, + audio: audio, + analyser: null, + data: null + } + + // this._audio.play(); + + // audio + const audioContext = new AudioContext(); + let resume = _ => { audioContext.resume() } + if (audioContext.state === 'suspended') { + document.body.addEventListener('touchend', resume, false); + document.body.addEventListener('click', resume, false); + } + + const source = audioContext.createMediaElementSource(audio); + + // // audioContext.createMediaStreamSource() + const analyser = audioContext.createAnalyser(); + analyser.fftSize = 2048; + + source.connect(analyser); + analyser.connect(audioContext.destination); + + const bufferLength = analyser.fftSize;//analyser.frequencyBinCount; + // const bufferLength = analyser.frequencyBinCount; + const data = new Uint8Array(bufferLength); + // analyser.getByteTimeDomainData(data); + analyser.getByteFrequencyData(data); + + // storage that will have the data on WGSL + this.addStorageMap(name, data, + // `array` + 'Sound' // custom struct in defaultStructs.js + ); + // uniform that will have the data length as a quick reference + this.addUniform(`${name}Length`, analyser.frequencyBinCount); + + sound.analyser = analyser; + sound.data = data; + this._sounds.push(sound); + + return audio; + } + + // TODO: verify this method addTextureStorage2d(name, shaderType) { if (this._nameExists(this._texturesStorage2d, name)) { return; @@ -617,6 +571,27 @@ export default class Points { } /** + * Listen for an event dispatched from WGSL code + * @param {Number} id Number that represents an event Id + * @param {Function} callback function to be called when the event occurs + */ + addEventListener(name, callback, structSize) { + // this extra 1 is for the boolean flag in the Event struct + let data = Array(structSize + 1).fill(0); + this.addStorageMap(name, data, 'Event', true); + this._events.set(this._events_ids, + { + id: this._events_ids, + name: name, + callback: callback, + } + ); + + ++this._events_ids; + } + + /** + * @private * for internal use: * to flag add* methods and variables as part of the RenderPasses */ @@ -625,9 +600,9 @@ export default class Points { } /** - * + * @private * @param {ShaderType} shaderType - * @param {boolean} internal + * @param {boolean} internal * @returns string with bindings */ _createDynamicGroupBindings(shaderType, internal) { @@ -731,6 +706,7 @@ export default class Points { this._numRows = numRows; } + /** @private */ _compileRenderPass = (renderPass, index) => { let vertexShader = renderPass.vertexShader; let computeShader = renderPass.computeShader; @@ -746,7 +722,8 @@ export default class Points { let dynamicStructParams = ''; this._uniforms.forEach(variable => { - dynamicStructParams += /*wgsl*/`${variable.name}:f32, \n\t`; + let uniformType = variable.type || 'f32'; + dynamicStructParams += /*wgsl*/`${variable.name}:${uniformType}, \n\t`; }); if (this._uniforms.length) { @@ -874,26 +851,26 @@ export default class Points { this.addPoint(coordinate, this._canvas.clientWidth / this._numColumns, this._canvas.clientHeight / this._numRows, colors); } } - this.createVertexBuffer(new Float32Array(this._vertexArray)); + this._createVertexBuffer(new Float32Array(this._vertexArray)); } - this.createComputeBuffers(); + this._createComputeBuffers(); - await this.createPipeline(); + await this._createPipeline(); } /** - * + * @private * @param {Float32Array} vertexArray * @returns buffer */ - createVertexBuffer(vertexArray) { + _createVertexBuffer(vertexArray) { this._vertexBufferInfo = new VertexBufferInfo(vertexArray); this._buffer = this._createAndMapBuffer(vertexArray, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST); } /** - * + * @private * @param {Float32Array} data * @param {GPUBufferUsageFlags} usage * @param {Boolean} mappedAtCreation @@ -913,6 +890,7 @@ export default class Points { /** + * @private * It creates with size, no with data, so it's empty * @param {Number} size numItems * instanceByteSize ; * @param {GPUBufferUsageFlags} usage @@ -926,31 +904,34 @@ export default class Points { return buffer } + /** @private */ _createParametersUniforms() { const values = new Float32Array(this._uniforms.map(v => v.value)); this._uniforms.buffer = this._createAndMapBuffer(values, GPUBufferUsage.UNIFORM); } - createComputeBuffers() { + /** @private */ + _createComputeBuffers() { //-------------------------------------------- this._createParametersUniforms(); //-------------------------------------------- this._storage.forEach(storageItem => { + let usage = GPUBufferUsage.STORAGE; + if (storageItem.read) { + usage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC; + } + storageItem.usage = usage; if (storageItem.mapped) { const values = new Float32Array(storageItem.array); - storageItem.buffer = this._createAndMapBuffer(values, GPUBufferUsage.STORAGE); + storageItem.buffer = this._createAndMapBuffer(values, usage); } else { - let usage = GPUBufferUsage.STORAGE; - if (storageItem.read) { - usage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC; - } storageItem.buffer = this._createBuffer(storageItem.size * storageItem.structSize * 4, usage); } }); //-------------------------------------------- this._readStorage.forEach(readStorageItem => { readStorageItem.buffer = this._device.createBuffer({ - size: readStorageItem.size, + size: readStorageItem.size * 4, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); }); @@ -1020,6 +1001,7 @@ export default class Points { }); } + /** @private */ _createComputeBindGroup() { this._renderPasses.forEach((renderPass, index) => { if (renderPass.hasComputeShader) { @@ -1053,7 +1035,8 @@ export default class Points { }); } - async createPipeline() { + /** @private */ + async _createPipeline() { this._createComputeBindGroup(); @@ -1063,7 +1046,7 @@ export default class Points { layout: this._device.createPipelineLayout({ bindGroupLayouts: [renderPass.bindGroupLayout] }), - label: `createPipeline() - ${index}`, + label: `_createPipeline() - ${index}`, compute: { module: this._device.createShaderModule({ code: renderPass.compiledShaders.compute @@ -1171,6 +1154,7 @@ export default class Points { } /** + * @private * Creates the entries for the pipeline * @returns an array with the entries */ @@ -1348,6 +1332,7 @@ export default class Points { return entries; } + /** @private */ _createParams() { this._renderPasses.forEach(renderPass => { @@ -1388,7 +1373,7 @@ export default class Points { } - update() { + async update() { if (!this._canvas) return; if (!this._device) return; @@ -1414,10 +1399,17 @@ export default class Points { this._storage.forEach(storageItem => { if (storageItem.mapped) { const values = new Float32Array(storageItem.array); - storageItem.buffer = this._createAndMapBuffer(values, GPUBufferUsage.STORAGE); + storageItem.buffer = this._createAndMapBuffer(values, storageItem.usage); } }); + // AUDIO + // this._analyser.getByteTimeDomainData(this._dataArray); + this._sounds.forEach(sound => { + sound.analyser?.getByteFrequencyData(sound.data); + }); + // END AUDIO + this._texturesExternal.forEach(externalTexture => { externalTexture.texture = this._device.importExternalTexture({ source: externalTexture.video @@ -1507,7 +1499,7 @@ export default class Points { - if (this._readStorage.length && !this._readStorageCopied) { + if (this._readStorage.length) { this._readStorage.forEach(readStorageItem => { let storageItem = this._storage.find(storageItem => storageItem.name === readStorageItem.name); @@ -1516,10 +1508,9 @@ export default class Points { 0 /* source offset */, readStorageItem.buffer /* destination buffer */, 0 /* destination offset */, - readStorageItem.size /* size */ + readStorageItem.buffer.size /* size */ ); }); - this._readStorageCopied = true; } // --------------------- @@ -1536,8 +1527,23 @@ export default class Points { this._mouseWheel = false; this._mouseDeltaX = 0; this._mouseDeltaY = 0; + + await this.read(); + } + + async read() { + for (const [key, event] of this._events) { + let eventRead = await this.readStorage(event.name); + if (eventRead) { + let id = eventRead[0]; + if (id != 0) { + event.callback && event.callback(eventRead.slice(1, -1)); + } + } + } } + /** @private */ _getWGSLCoordinate(value, side, invert = false) { const direction = invert ? -1 : 1; const p = value / side; @@ -1638,7 +1644,7 @@ export default class Points { const options = { audioBitsPerSecond: 128000, videoBitsPerSecond: 6000000, - mimeType: "video/webm", + mimeType: 'video/webm', }; this.videoStream = this._canvas.captureStream(60); this.mediaRecorder = new MediaRecorder(this.videoStream, options); diff --git a/src/core/RenderPasses/filmgrain/index.js b/src/core/RenderPasses/filmgrain/index.js index bc538274..e06fb292 100644 --- a/src/core/RenderPasses/filmgrain/index.js +++ b/src/core/RenderPasses/filmgrain/index.js @@ -1,6 +1,6 @@ import vertexShader from './vert.js'; import fragmentShader from './frag.js'; -import { ShaderType } from '../../../absulit.points.module.js'; +import ShaderType from '../../../absulit.points.module.js'; const filmgrain = { vertexShader, diff --git a/src/core/audio.js b/src/core/audio.js new file mode 100644 index 00000000..5af294db --- /dev/null +++ b/src/core/audio.js @@ -0,0 +1,17 @@ +export const audioAverage = /*wgsl*/` +fn audioAverage(sound:Sound) -> f32 { + var audioAverage = 0.; + for (var index = 0; index < i32(params.audioLength); index++) { + let audioValue = sound.data[index] / 256; + audioAverage += audioValue; + } + return audioAverage / params.audioLength; +} +`; + +export const audioAverageSegments = /*wgsl*/` +fn audioAverageSegments(segmentNum:i32) -> f32{ + // arrayLength(&array) + return .0; +} +`; diff --git a/src/core/defaultStructs.js b/src/core/defaultStructs.js index 4fb4689f..9ad444f6 100644 --- a/src/core/defaultStructs.js +++ b/src/core/defaultStructs.js @@ -8,6 +8,19 @@ struct Fragment { @location(3) uvr: vec2, @location(4) mouse: vec2 } + +struct Sound { + data: array, + //play + //dataLength + //duration + //currentPosition +} + +struct Event { + updated: u32, + data: array +} `; export default defaultStructs; diff --git a/src/core/image.js b/src/core/image.js index adb898f3..794d8523 100644 --- a/src/core/image.js +++ b/src/core/image.js @@ -11,7 +11,7 @@ fn texturePosition(texture:texture_2d, aSampler:sampler, position:vec2 let flipTexture = vec2(1.,-1.); let flipTextureCoordinates = vec2(-1.,1.); let dims: vec2 = textureDimensions(texture, 0); - let dimsF32 = vec2(f32(dims.x), f32(dims.y)); + let dimsF32 = vec2(dims); let minScreenSize = min(params.screenHeight, params.screenWidth); let imageRatio = dimsF32 / minScreenSize; @@ -171,6 +171,6 @@ fn pixelateTexturePosition(texture:texture_2d, textureSampler:sampler, posi let coord = vec2(dx*floor( uv.x / dx), dy * floor( uv.y / dy)); //texturePosition(texture:texture_2d, aSampler:sampler, position:vec2, uv:vec2, crop:bool) -> vec4 { - return texturePosition(texture, textureSampler, position, coord, false); + return texturePosition(texture, textureSampler, position, coord, true); } `;