Commit 43b1f7d6 authored by Matus Talcik's avatar Matus Talcik
Browse files

add template

parent ca8de4f7
Pipeline #137285 passed with stage
in 2 minutes and 24 seconds
dist/out.js
node_modules
This is a template for any playground of Chromatin.
\ No newline at end of file
This diff is collapsed.
{
"name": "template",
"version": "1.0.0",
"description": "",
"main": "src/index.tsx",
"scripts": {
"build": "node scripts/build.js",
"watch": "node scripts/watch.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@webgpu/types": "^0.1.15",
"esbuild": "^0.14.38",
"gl-matrix": "^3.4.3",
"image-js": "^0.34.0",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"devDependencies": {
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.3",
"typescript": "^4.6.4"
}
}
require('esbuild').build({
entryPoints: ['src/index.tsx'],
bundle: true,
outfile: 'dist/out.js',
loader: {
'.wgsl': 'text',
'.png': 'file',
},
preserveSymlinks: true,
}).catch(() => process.exit(1))
\ No newline at end of file
require('esbuild').build({
entryPoints: ['src/index.tsx'],
outfile: 'dist/out.js',
bundle: true,
loader: {
'.wgsl': 'text',
'.png': 'file',
},
preserveSymlinks: true,
watch: {
onRebuild(error, result) {
if (error) console.error('watch build failed:', error)
else console.log('watch build succeeded:', result)
},
},
}).then(result => {
console.log('watching...')
})
\ No newline at end of file
* {
box-sizing: border-box;
margin: 0px;
padding: 0px;
}
html, body, #root {
width: 100vw;
height: 100vh;
overflow: hidden;
}
#root {
position: absolute;
}
\ No newline at end of file
import React, { useState, useEffect, useRef } from 'react';
import { GraphicsLibrary } from "./modules/graphics/index";
import * as GraphicsModule from "./modules/graphics";
import './App.css';
export function App(): JSX.Element {
const [adapter, setAdapter] = useState<GPUAdapter | null>(null);
const [device, setDevice] = useState<GPUDevice | null>(null);
const [deviceError, setDeviceError] = useState<GPUUncapturedErrorEvent | null>(null);
const [graphicsLibrary, setGraphicsLibrary] = useState<GraphicsLibrary | null>(null);
const canvasElement = useRef<HTMLCanvasElement>(null);
const [viewport, setViewport] = useState<GraphicsModule.ChromatinViewport | null>(null);
//#region Adapter, Device, Library Initialization
useEffect(() => {
async function waitForAdapter() {
const adapter = await navigator.gpu.requestAdapter({
powerPreference: "high-performance",
});
setAdapter(adapter);
}
waitForAdapter();
}, []);
useEffect(() => {
if (adapter == null) {
return;
}
const waitForDevice = async function () {
const device = await adapter.requestDevice({
// requiredFeatures: ['timestamp-query']
});
device.onuncapturederror = (error: GPUUncapturedErrorEvent) => {
setDeviceError(error);
};
setDeviceError(null);
setDevice(device);
}
waitForDevice();
}, [adapter]);
useEffect(() => {
if (adapter == null || device == null) {
return;
}
setGraphicsLibrary(() => new GraphicsLibrary(adapter, device));
}, [adapter, device]);
//#endregion
//#region Viewport Setup
useEffect(() => {
if (!graphicsLibrary || canvasElement == null || !canvasElement.current) return;
const newViewport = graphicsLibrary.createChromatinViewport(canvasElement.current);
setViewport(() => newViewport);
// Draw the scene repeatedly
const render = async (frametime: number) => {
await newViewport.render(frametime);
requestAnimationFrame(render);
}
const requestID = requestAnimationFrame(render);
return function cleanup() {
viewport?.deallocate();
window.cancelAnimationFrame(requestID);
};
}, [graphicsLibrary, canvasElement]);
//#endregion Viewport Setup
return (
<canvas ref={canvasElement} style={{ width: '100%', height: '100%', overflow: 'hidden' }}></canvas>
);
}
\ No newline at end of file
import React from 'react';
import { createRoot } from 'react-dom/client';
import { StrictMode } from 'react';
import { App } from './App';
const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>);
const kiloByte = 1024;
const megaByte = 1024 * kiloByte;
export class ByteRange {
public start = 0;
public end = 0;
}
export interface ArrayViews {
data: DataView;
f32View: Float32Array;
i32View: Int32Array;
u32View: Uint32Array;
}
export class LinearImmutableArray {
private _allocator: SlabAllocator;
private id: number;
private _blockIdx: number;
//#region Views
private _data: DataView;
private _u8view: Uint8Array;
private _f32View: Float32Array;
private _i32View: Int32Array;
private _u32View: Uint32Array;
//#endregion
private _globalOffsetSlabs: number;
private _localOffsetSlabs: number;
private _sizeSlabs: number;
constructor(
allocator: SlabAllocator,
id: number,
blockIdx: number,
data: DataView,
globalOffsetSlabs: number,
localOffsetSlabs: number,
sizeSlabs: number
) {
this._allocator = allocator;
this.id = id;
this._blockIdx = blockIdx;
this._data = data;
this._u8view = new Uint8Array(data.buffer, data.byteOffset);
this._f32View = new Float32Array(data.buffer, data.byteOffset);
this._i32View = new Int32Array(data.buffer, data.byteOffset);
this._u32View = new Uint32Array(data.buffer, data.byteOffset);
this._globalOffsetSlabs = globalOffsetSlabs;
this._localOffsetSlabs = localOffsetSlabs;
this._sizeSlabs = sizeSlabs;
}
public setModifiedBytes(ranges: ByteRange): void {
const startSlab = Math.floor(ranges.start / this._allocator.slabSize);
const endSlab = Math.ceil(ranges.end / this._allocator.slabSize);
this.setModifiedSlabs(Array.from({ length: endSlab - startSlab + 1 }, (_, i) => i + startSlab));
}
public setModifiedSlabs(indices: Array<number>): void {
const globalIndices = indices.map(idx => idx + this._localOffsetSlabs);
this._allocator.setModified(globalIndices);
}
public removeSlabs(count: number): void {
this._sizeSlabs -= count;
}
public uploadModified(queue: GPUQueue): void {
for (let i = this.globalOffsetSlabs, j = 0; i < this.globalOffsetSlabs + this.sizeSlabs; i++, j++) {
if (this._allocator.modifiedSlabs[i]) {
this._allocator.modifiedSlabs[i] = false;
const offset = this.data.byteOffset + j * this._allocator.slabSize;
queue.writeBuffer(this.gpuBuffer, offset, this.data.buffer, offset, this._allocator.slabSize);
}
}
}
public get allocator(): SlabAllocator {
return this._allocator;
}
public get blockIdx(): number {
return this._blockIdx;
}
public get globalOffsetSlabs(): number {
return this._globalOffsetSlabs;
}
public get localOffsetSlabs(): number {
return this._localOffsetSlabs;
}
public get sizeBytes(): number {
return this._sizeSlabs * this._allocator.slabSize;
}
public get sizeSlabs(): number {
return this._sizeSlabs;
}
public get data(): DataView {
return this._data;
}
public cpuBuffer(): ArrayBuffer {
return this._allocator.getCpuBlock(this._blockIdx);
}
public get gpuBuffer(): GPUBuffer {
return this._allocator.getGpuBlock(this._blockIdx);
}
//#region ArrayViews Interface
public get u8view(): Uint8Array {
return this._u8view;
}
public get f32View(): Float32Array {
return this._f32View;
}
public get i32View(): Int32Array {
return this._i32View;
}
public get u32View(): Uint32Array {
return this._u32View;
}
//#endregion
}
/**
*
*/
export class SlabAllocator {
//#region Variables
private _device: GPUDevice;
//#region Information about memory layout
private _blockSize: number;
private _slabSize: number;
private _slabsPerBlock: number;
//#endregion
//#region Memory blocks
private _cpuBlocks: Array<ArrayBuffer> = [];
private _gpuBlocks: Array<GPUBuffer> = [];
//#endregion
//#region Information about slabs
private _allocatedSlabs: Array<boolean> = [];
private _modifiedSlabs: Array<boolean> = [];
//#endregion
//#region Slab ownership info
private owners = 0;
private ownerSlabs: Array<number> = [];
//#endregion
//#endregion
constructor(
device: GPUDevice,
blockSize: number = 128 * megaByte,
slabSize: number = 1 * megaByte,
) {
this._device = device;
this._blockSize = blockSize;
this._slabSize = slabSize;
this._slabsPerBlock = this._blockSize / this._slabSize;
this.createNewBlock();
}
/**
*
*/
private createNewBlock() {
this._cpuBlocks.push(new ArrayBuffer(this._blockSize));
this._gpuBlocks.push(this._device.createBuffer({
size: this._blockSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
}));
this._allocatedSlabs.push(...new Array(this._slabsPerBlock).fill(false));
this.ownerSlabs.push(...new Array(this._slabsPerBlock).fill(0));
this._modifiedSlabs.push(...new Array(this._slabsPerBlock).fill(true));
}
public allocateArray(slabsCount: number): LinearImmutableArray {
this.owners = this.owners + 1;
let globalOffsetSlabs = 0;
let localOffsetSlabs = 0;
let blockIdx = 0;
let found = 0;
for (; blockIdx < this._cpuBlocks.length; blockIdx++) {
localOffsetSlabs = 0;
found = 0;
for (let slabIdx = 0; slabIdx < this._slabsPerBlock; slabIdx++) {
if (this._allocatedSlabs[globalOffsetSlabs + slabIdx] == false) {
found += 1;
if (found == slabsCount) {
break;
}
} else {
localOffsetSlabs = slabIdx + 1;
found = 0;
}
}
if (found == slabsCount) {
break;
}
globalOffsetSlabs += this._slabsPerBlock;
}
if (found == slabsCount) {
globalOffsetSlabs += localOffsetSlabs;
} else {
this.createNewBlock();
blockIdx = this._modifiedSlabs.length - 1;
localOffsetSlabs = 0;
globalOffsetSlabs = blockIdx * this._slabsPerBlock;
}
const dataView = new DataView(this._cpuBlocks[blockIdx], localOffsetSlabs * this._slabSize, slabsCount * this._slabSize);
for (let i = 0; i < slabsCount; i++) {
this._allocatedSlabs[globalOffsetSlabs + i] = true;
this._modifiedSlabs[globalOffsetSlabs + i] = true;
}
return new LinearImmutableArray(
this,
this.owners,
blockIdx,
dataView,
globalOffsetSlabs,
localOffsetSlabs,
slabsCount,
);
}
public reallocateArray(array: LinearImmutableArray, slabsCount: number): LinearImmutableArray {
const changeSlabs = slabsCount - array.sizeSlabs;
if (changeSlabs == 0) {
return array;
}
if (changeSlabs < 0) {
for (let i = array.localOffsetSlabs + array.sizeSlabs + changeSlabs; i < array.localOffsetSlabs + array.sizeSlabs; i++) {
this._allocatedSlabs[array.globalOffsetSlabs + i] = false;
}
array.removeSlabs(changeSlabs);
return array;
}
// Copy from old to new array
const newArray = this.allocateArray(slabsCount);
const oldArrayView = new Uint8Array(this._cpuBlocks[array.blockIdx], array.localOffsetSlabs * this.slabSize, array.sizeSlabs * this.slabSize);
const newArrayView = new Uint8Array(this._cpuBlocks[newArray.blockIdx], newArray.localOffsetSlabs * this.slabSize, array.sizeSlabs * this.slabSize);
newArrayView.set(oldArrayView);
// Flag old slabs as deallocated
for (let i = 0; i < array.sizeSlabs; i++) {
this._allocatedSlabs[array.globalOffsetSlabs + i] = false;
}
return newArray;
}
public deallocateArray(array: LinearImmutableArray | null): void {
if (!array) {
return;
}
for (let i = 0; i < array.sizeSlabs; i++) {
this._allocatedSlabs[array.globalOffsetSlabs + i] = false;
}
}
public setModified(indices: Array<number>): void {
indices.forEach((index) => this._modifiedSlabs[index] = true);
}
//#region Fragmentation
/**
* TODO
*/
public defragment() {
// TODO
}
//#endregion
//#region Accessors
public get device(): GPUDevice {
return this._device;
}
public get blockSize(): number {
return this._blockSize;
}
public get slabSize(): number {
return this._slabSize;
}
public get slabsPerBlock(): number {
return this._slabsPerBlock
}
public get modifiedSlabs(): Array<boolean> {
return this._modifiedSlabs;
}
public getCpuBlock(idx: number): ArrayBuffer {
return this._cpuBlocks[idx];
}
public getGpuBlock(idx: number): GPUBuffer {
return this._gpuBlocks[idx];
}
//#endregion
}
import { GraphicsLibrary } from './index';
const tileDim = 128;
const batch = [4, 4];
export class Blur {
private graphicsLibrary: GraphicsLibrary;
private _width = 0;
private _height = 0;
private _filterDimension = 0;
private _blockDimension = 0;
private parametersGPU: GPUBuffer;
private flipGPU: Array<GPUBuffer> = new Array(2);
private computeConstantsBindGroup: GPUBindGroup | null = null;
private computeBindGroup0: GPUBindGroup | null = null;
private computeBindGroup1: GPUBindGroup | null = null;
private _textures: [GPUTexture, GPUTexture] | null = null;
constructor(graphicsLibrary: GraphicsLibrary) {
this.graphicsLibrary = graphicsLibrary;
const device = this.graphicsLibrary.device;
this.flipGPU[0] = device.createBuffer({
size: 4,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(this.flipGPU[0], 0, new Float32Array([0]), 0, 1);
this.flipGPU[1] = device.createBuffer({
size: 4,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(this.flipGPU[1], 0, new Float32Array([1]), 0, 1);
this.parametersGPU = device.createBuffer({
size: 8,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
});
}
private createBindGroups() {
if (!this._textures) {
return;
}
const device = this.graphicsLibrary.device;
const pipeline = this.graphicsLibrary.computePipelines.ambientOcclusionBlur;
this.computeConstantsBindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: this.graphicsLibrary.linearSampler },
{ binding: 1, resource: { buffer: this.parametersGPU } },
],
});
this.computeBindGroup0 = device.createBindGroup({
layout: this.graphicsLibrary.bindGroupLayouts.aoBlurIO,
entries: [
{ binding: 0, resource: this._textures[0].createView() },
{ binding: 1, resource: this._textures[1].createView() },
{ binding: 2, resource: { buffer: this.flipGPU[0] } },
],
});
this.computeBindGroup1 = device.createBindGroup({