/* Phaneron - Clustered, accelerated and cloud-fit video server, pre-assembled and in kit form. Copyright (C) 2020 Streampunk Media Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import { ProcessImpl } from './imageProcess' import { clContext as nodenCLContext, OpenCLBuffer, KernelParams } from 'nodencl' import { matrixFlatten, matrixMultiply } from './colourMaths' const transformKernel = ` __constant sampler_t samplerIn = CLK_NORMALIZED_COORDS_TRUE | CLK_ADDRESS_CLAMP | CLK_FILTER_LINEAR; __constant sampler_t samplerOut = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST; __kernel void transform( __read_only image2d_t input, __global float4* restrict transformMatrix, __write_only image2d_t output) { int w = get_image_width(output); int h = get_image_height(output); // Load two rows of the 3x3 transform matrix via two float4s float4 tmpMat0 = transformMatrix[0]; float4 tmpMat1 = transformMatrix[1]; float3 mat0 = (float3)(tmpMat0.s0, tmpMat0.s1, tmpMat0.s2); float3 mat1 = (float3)(tmpMat0.s3, tmpMat1.s0, tmpMat1.s1); int outX = get_global_id(0); int outY = get_global_id(1); int2 posOut = {outX, outY}; float3 inPos = (float3)(outX / (float) w - 0.5f, outY / (float) h - 0.5f, 1.0f); float2 posIn = (float2)(dot(mat0, inPos) + 0.5f, dot(mat1, inPos) + 0.5f); float4 in = read_imagef(input, samplerIn, posIn); write_imagef(output, posOut, in); } ` export default class Transform extends ProcessImpl { clContext: nodenCLContext transformMatrix: Array transformArray: Float32Array matrixBuffer: OpenCLBuffer | null = null curParams: KernelParams | null = null constructor(clContext: nodenCLContext, width: number, height: number) { super('transform', width, height, transformKernel, 'transform') this.clContext = clContext this.transformMatrix = [...new Array(3)].map(() => new Float32Array(3)) this.transformMatrix[0] = Float32Array.from([1.0, 0.0, 0.0]) this.transformMatrix[1] = Float32Array.from([0.0, 1.0, 0.0]) this.transformMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) this.transformArray = matrixFlatten(this.transformMatrix) } private async updateMatrix(clQueue: number): Promise { if (!this.matrixBuffer) throw new Error('Transform needs to be initialised') this.transformArray = matrixFlatten(this.transformMatrix) await this.matrixBuffer.hostAccess( 'writeonly', clQueue, Buffer.from(this.transformArray.buffer) ) return this.matrixBuffer.hostAccess('none', clQueue) } async init(): Promise { this.matrixBuffer = await this.clContext.createBuffer( this.transformArray.byteLength, 'readonly', 'coarse', undefined, 'transformMatrix' ) await this.updateMatrix(this.clContext.queue.load) return this.clContext.waitFinish(this.clContext.queue.load) } private checkParamsChange(params: KernelParams): boolean { return ( this.curParams !== null && params.flipH === this.curParams.flipH && params.flipV === this.curParams.flipV && params.anchorX === this.curParams.anchorX && params.anchorY === this.curParams.anchorY && params.scaleX === this.curParams.scaleX && params.scaleY === this.curParams.scaleY && params.offsetX === this.curParams.offsetX && params.offsetY === this.curParams.offsetY && params.rotate === this.curParams.rotate ) } async getKernelParams(params: KernelParams): Promise { if (!this.checkParamsChange(params)) { const aspect = this.width / this.height const flipX = (params.flipH as boolean) || false ? -1.0 : 1.0 const flipY = (params.flipV as boolean) || false ? -1.0 : 1.0 const anchorX = (params.anchorX as number) || 0.0 const anchorY = (params.anchorY as number) || 0.0 const scaleX = ((params.scaleX as number) || 1.0) * flipX const scaleY = ((params.scaleY as number) || 1.0) * flipY const offsetX = (params.offsetX as number) || 0.0 const offsetY = (params.offsetY as number) || 0.0 const rotate = ((params.rotate as number) || 0.0) * 2 * Math.PI const anchorInMatrix = [...new Array(3)].map(() => new Float32Array(3)) anchorInMatrix[0] = Float32Array.from([1.0, 0.0, anchorX]) anchorInMatrix[1] = Float32Array.from([0.0, 1.0, anchorY]) anchorInMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) const scaleMatrix = [...new Array(3)].map(() => new Float32Array(3)) scaleMatrix[0] = Float32Array.from([1.0 / (scaleX * aspect), 0.0, 0.0]) scaleMatrix[1] = Float32Array.from([0.0, 1.0 / scaleY, 0.0]) scaleMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) const rotateMatrix = [...new Array(3)].map(() => new Float32Array(3)) rotateMatrix[0] = Float32Array.from([Math.cos(rotate), -Math.sin(rotate), 0.0]) rotateMatrix[1] = Float32Array.from([Math.sin(rotate), Math.cos(rotate), 0.0]) rotateMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) const translateMatrix = [...new Array(3)].map(() => new Float32Array(3)) translateMatrix[0] = Float32Array.from([1.0, 0.0, offsetX * aspect]) translateMatrix[1] = Float32Array.from([0.0, 1.0, offsetY]) translateMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) const anchorOutMatrix = [...new Array(3)].map(() => new Float32Array(3)) anchorOutMatrix[0] = Float32Array.from([1.0, 0.0, -anchorX * aspect]) anchorOutMatrix[1] = Float32Array.from([0.0, 1.0, -anchorY]) anchorOutMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) const projectMatrix = [...new Array(3)].map(() => new Float32Array(3)) projectMatrix[0] = Float32Array.from([aspect, 0.0, 0.0]) projectMatrix[1] = Float32Array.from([0.0, 1.0, 0.0]) projectMatrix[2] = Float32Array.from([0.0, 0.0, 1.0]) this.transformMatrix = matrixMultiply( matrixMultiply( matrixMultiply( matrixMultiply(matrixMultiply(anchorInMatrix, scaleMatrix), rotateMatrix), translateMatrix ), anchorOutMatrix ), projectMatrix ) await this.updateMatrix(this.clContext.queue.load) await this.clContext.waitFinish(this.clContext.queue.load) } this.curParams = params this.matrixBuffer?.addRef() return Promise.resolve({ input: params.input, transformMatrix: this.matrixBuffer, output: params.output }) } releaseRefs(): void { this.matrixBuffer?.release() } }