From 7e2947675a23d712f8608e24488f7d0d66c8ac89 Mon Sep 17 00:00:00 2001 From: Xu Xing Date: Tue, 25 Aug 2020 16:29:18 +0800 Subject: [PATCH] Add case to reproduce writeTexture and rowsPerImage issue --- demo/common.js | 12 +- demo/index_add.js | 10 +- demo/utils.js | 2 +- src/add_texturer32f.ts | 2 +- src/tex_util.ts | 4 +- src/texture.ts | 263 ++++++++++++++++++++++++++++++----------- 6 files changed, 217 insertions(+), 76 deletions(-) diff --git a/demo/common.js b/demo/common.js index 31f5f89..4ff01bf 100644 --- a/demo/common.js +++ b/demo/common.js @@ -167,6 +167,9 @@ export async function runTestAdd(device, glslang, size_x = 4096, size_y = 256, t device, glslang, firstMatrix, secondMatrix, size_x, size_y, shape); if (error) return; } + if (trials == 0) { + return; + } { const addOp = new compute.AddBufferOp( @@ -187,17 +190,21 @@ export async function runTestAdd(device, glslang, size_x = 4096, size_y = 256, t await utils.time( addOp, utils.executeOp, ' Add texture rgba32float ', trials, reps); } + } export async function checkCorrectnessAdd( device, glslang, firstMatrix, secondMatrix, size_x, size_y, shape) { let errorSummary = {error: 0}; + /* + { const op = new compute.AddBufferOp( device, glslang, firstMatrix, secondMatrix, shape); await utils.executeCompareAndDisposeAdd( op, firstMatrix, secondMatrix, size_x, size_y, errorSummary); } + */ { const op = new compute.AddTextureR32FOp( @@ -205,13 +212,16 @@ export async function checkCorrectnessAdd( await utils.executeCompareAndDisposeAdd( op, firstMatrix, secondMatrix, size_x, size_y, errorSummary); } + console.log("-----------------------------------------------------------------"); + /* { const op = new compute.AddTextureOp( device, glslang, firstMatrix, secondMatrix, shape, 'rgba32float'); await utils.executeCompareAndDisposeAdd( op, firstMatrix, secondMatrix, size_x, size_y, errorSummary); } - + */ + return errorSummary.error; } diff --git a/demo/index_add.js b/demo/index_add.js index 9c51d04..62f7e14 100644 --- a/demo/index_add.js +++ b/demo/index_add.js @@ -14,6 +14,12 @@ import * as common from './common.js'; const enableTimeStamp = false; const device = await adapter.requestDevice(); const glslang = await glslangInit(); - await common.runTestAdd(device, glslang, 4096, 256, 50,50); - await common.runTestAdd(device, glslang, 4096, 1024, 50,50); + await common.runTestAdd(device, glslang, 10, 3, 0,0); + // await common.runTestAdd(device, glslang, 10, 3, 0,0); + await common.runTestAdd(device, glslang, 35, 15, 0,0); + // await common.runTestAdd(device, glslang, 10, 20, 0,0); + //await common.runTestAdd(device, glslang, 200, 100, 0,0); + await common.runTestAdd(device, glslang, 4096, 128, 0,0); + // await common.runTestAdd(device, glslang, 4096, 256, 0,0); + // await common.runTestAdd(device, glslang, 4096, 512, 0,0); })(); \ No newline at end of file diff --git a/demo/utils.js b/demo/utils.js index cd42090..89c54f2 100644 --- a/demo/utils.js +++ b/demo/utils.js @@ -1,7 +1,7 @@ export function createFloat32Array(w, h) { let matrix = new Float32Array(w * h); for (let i = 0; i < w * h; i++) { - matrix[i] = Math.random(); + matrix[i] = i;// Math.random(); } return matrix; } diff --git a/src/add_texturer32f.ts b/src/add_texturer32f.ts index 43f8a90..9037a8d 100644 --- a/src/add_texturer32f.ts +++ b/src/add_texturer32f.ts @@ -9,7 +9,7 @@ export class AddTextureR32FOp extends TextureOp { secondMatrix: Float32Array|Uint32Array, shape: Uint32Array, format: GPUTextureFormat) { super(device, glslang, format); - const TS = 16; + const TS = 1; this.workGroupSize = [TS, TS, 1]; this.compile(firstMatrix, secondMatrix, shape, this.getShader()); } diff --git a/src/tex_util.ts b/src/tex_util.ts index 6c60d07..d09ac47 100644 --- a/src/tex_util.ts +++ b/src/tex_util.ts @@ -123,7 +123,9 @@ export function getPackedMatrixTextureShapeWidthHeight( if (format == 'rgba32float' || format == 'rgba32uint') // return [Math.max(1, Math.ceil(rows)), Math.max(1, Math.ceil(columns / // 4))]; - return [Math.max(1, Math.ceil(rows / 4)), Math.max(1, Math.ceil(columns))]; + return [ + Math.max(1, Math.ceil(rows / 2)), Math.max(1, Math.ceil(columns / 2)) + ]; else if (format == 'rgba8uint') return [rows, columns]; else diff --git a/src/texture.ts b/src/texture.ts index e55e2b9..ac4ea29 100644 --- a/src/texture.ts +++ b/src/texture.ts @@ -28,8 +28,57 @@ export class TextureOp { this.bufferID = 0; } - private copyFromHostBufferToDeviceTexture( - src: GPUBuffer, width: number, height: number) { + // From: Dawn:ComputeTextureCopyBufferSize + // TODO: Make this works with different input size + getBufferSize() { + /* + const blockHeight = 1; + const blockWidth = 1; + + const [widthTex, heightTex] = + tex_util.getPackedMatrixTextureShapeWidthHeight( + this.shape[0], this.shape[1], this.format); + + const bytesPerRow = tex_util.getBytesPerRow(widthTex, this.kBytesPerTexel); + + const sliceSize = bytesPerRow * (heightTex / blockHeight - 1) + + (widthTex / blockWidth) * this.kBytesPerTexel; + */ + /* + this.shape = 4096, 128, this.kBytesPerTexel=16 + texture.ts:56 bytesPerRow = 16384, heightTex =128 + texture.ts:125 this.getBufferSize() =2097152 + + */ + + const [widthTex, heightTex] = + tex_util.getPackedMatrixTextureShapeWidthHeight( + this.shape[0], this.shape[1], this.format); + /* + console.log( + ' this.shape = ' + this.shape[0] + ', ' + this.shape[1] + + ', this.kBytesPerTexel=' + this.kBytesPerTexel); + */ + + const bytesPerRow = tex_util.getBytesPerRow(widthTex, this.kBytesPerTexel); + /* + console.log( + ' bytesPerRow = ' + bytesPerRow + ' widthTex, heightTex =' + widthTex + + ', ' + heightTex); + */ + const sliceSize = bytesPerRow * heightTex; + return sliceSize; + } + + + getBufferSizeRead() { + return this.getBufferSize(); + } + + + /* + private writeTexture( + data: Float32Array|Uint32Array, width: number, height: number) { const [widthTex, heightTex] = tex_util.getPackedMatrixTextureShapeWidthHeight( width, height, this.format); @@ -40,90 +89,107 @@ export class TextureOp { usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE }); - const encoder = this.device.createCommandEncoder(); - // TODO: fix the width height. - // copyBufferToTexture(source, destination, copySize). + console.log('xx createTexture ' + width + ',' + height); + const bytesPerRow = tex_util.getBytesPerRow(widthTex, this.kBytesPerTexel); - // console.log('bytesPerRow=' + bytesPerRow); - encoder.copyBufferToTexture( - {buffer: src, bytesPerRow: bytesPerRow}, - {texture: texture, mipLevel: 0, origin: {x: 0, y: 0, z: 0}}, + console.log(heightTex + ', start ' + this.format); + this.queue.writeTexture( + {texture: texture}, data as ArrayBuffer, + {bytesPerRow: bytesPerRow}, //, rowsPerImage: 1}, // heightTex {width: widthTex, height: heightTex, depth: 1}); - this.device.defaultQueue.submit([encoder.finish()]); + console.log('xx writeTexture ' + widthTex + ',' + heightTex); return texture; } + */ + + + // This will add padding before uploading to GPU texture. + private addTexturePadding( + textureData: Float32Array|Uint32Array, + width: number, + height: number, + bytesPerRow: number, + ) { + let textureDataWithPadding = + new Float32Array(bytesPerRow / this.kBytesPerTexel * height); + + for (let y = 0; y < height; ++y) { + for (let x = 0; x < width; ++x) { + const dst = x + y * bytesPerRow / this.kBytesPerTexel; + const src = x + y * width; + textureDataWithPadding[dst] = textureData[src]; + } + } + return textureDataWithPadding; + } - // From: Dawn:ComputeTextureCopyBufferSize - // TODO: Make this works with different input size - getBufferSize() { - const blockHeight = 1; - const blockWidth = 1; + + private writeTextureWithCopy( + matrixData: Float32Array|Uint32Array, width: number, height: number) { + const src = this.device.createBuffer({ + mappedAtCreation: true, + size: this.getBufferSize(), // 640 * 4, // + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | + GPUBufferUsage.COPY_DST + }); + console.log('xx createTexture ' + width + ',' + height); const [widthTex, heightTex] = tex_util.getPackedMatrixTextureShapeWidthHeight( - this.shape[0], this.shape[1], this.format); + width, height, this.format); const bytesPerRow = tex_util.getBytesPerRow(widthTex, this.kBytesPerTexel); + console.log( + 'xx writeTextureWithCopy: widthTex = ' + widthTex + + '; heightTex = ' + heightTex + ', bytesPerRow=' + bytesPerRow); - const sliceSize = bytesPerRow * (heightTex / blockHeight - 1) + - (widthTex / blockWidth) * this.kBytesPerTexel; - return sliceSize; + console.log( + 'xx writeTextureWithCopy: this.getBufferSize() =' + + this.getBufferSize()); + // TODO: turn this into type of secondMatrix. + const matrixDataWithAlignment = + this.addTexturePadding(matrixData, width, height, bytesPerRow); + + new Float32Array(src.getMappedRange()).set(matrixDataWithAlignment); + src.unmap(); + + const texture = this.device.createTexture({ + size: {width: widthTex, height: heightTex, depth: 1}, + format: this.format, + usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | + GPUTextureUsage.STORAGE + }); + const encoder = this.device.createCommandEncoder(); + // TODO: fix the width height. + // copyBufferToTexture(source, destination, copySize). + encoder.copyBufferToTexture( + {buffer: src, bytesPerRow: bytesPerRow}, + {texture: texture, mipLevel: 0, origin: {x: 0, y: 0, z: 0}}, + {width: widthTex, height: heightTex, depth: 1}); + this.device.defaultQueue.submit([encoder.finish()]); + return texture; } + + compile( firstMatrix: Float32Array|Uint32Array, secondMatrix: Float32Array|Uint32Array, shape: Uint32Array, computeShaderCode: any) { this.shape = shape; - // console.log('B2T this.getBufferSize()=' + this.getBufferSize()); - /* - const [gpuBufferFirstMatrix, arrayBufferFirstMatrix] = - this.device.createBufferMapped({ - size: this.getBufferSize(), // (firstMatrix as - // Float32Array).byteLength, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | - GPUBufferUsage.COPY_DST - }); - new Float32Array(arrayBufferFirstMatrix).set(firstMatrix); - gpuBufferFirstMatrix.unmap(); - */ - const gpuBufferFirstMatrix = this.device.createBuffer({ - mappedAtCreation: true, - size: this.getBufferSize(), - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | - GPUBufferUsage.COPY_DST - }); - // TODO: turn this into type of secondMatrix. - new Float32Array(gpuBufferFirstMatrix.getMappedRange()).set(firstMatrix); - gpuBufferFirstMatrix.unmap(); + const gpuTextureFirstMatrix = + this.writeTextureWithCopy(firstMatrix, this.shape[0], this.shape[1]); + /* const gpuTextureFirstMatrix = + this.writeTexture(firstMatrix, this.shape[2], this.shape[3]); + */ - const gpuTextureFirstMatrix = this.copyFromHostBufferToDeviceTexture( - gpuBufferFirstMatrix, this.shape[0], this.shape[1]); + const gpuTextureSecondMatrix = + this.writeTextureWithCopy(secondMatrix, this.shape[2], this.shape[3]); - /* - const [gpuBufferSecondMatrix, arrayBufferSecondMatrix] = - this.device.createBufferMapped({ - size: this.getBufferSize(), //(secondMatrix as - // Float32Array).byteLength, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | - GPUBufferUsage.COPY_DST - }); - new Float32Array(arrayBufferSecondMatrix).set(secondMatrix); - gpuBufferSecondMatrix.unmap(); + /* const gpuTextureSecondMatrix = + this.writeTexture(secondMatrix, this.shape[2], this.shape[3]); */ - const gpuBufferSecondMatrix = this.device.createBuffer({ - mappedAtCreation: true, - size: this.getBufferSize(), - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | - GPUBufferUsage.COPY_DST - }); - // TODO: turn this into type of secondMatrix. - new Float32Array(gpuBufferSecondMatrix.getMappedRange()).set(secondMatrix); - gpuBufferSecondMatrix.unmap(); - - const gpuTextureSecondMatrix = this.copyFromHostBufferToDeviceTexture( - gpuBufferSecondMatrix, this.shape[2], this.shape[3]); // Result Matrix. this.resultMatrixTextureSize = @@ -132,6 +198,9 @@ export class TextureOp { const [widthTex, heightTex] = tex_util.getPackedMatrixTextureShapeWidthHeight( this.shape[4], this.shape[5], this.format); + console.log( + 'xx result createTexture w h =' + widthTex + ',' + heightTex + + ', this.format=' + this.format); this.resultMatrixTexture = this.device.createTexture({ size: {width: widthTex, height: heightTex, depth: 1}, format: this.format, @@ -147,6 +216,8 @@ export class TextureOp { new Uint32Array(shapeMapping).set(shape); shapeBuffer.unmap(); */ + + // TODO: make this buffer.destroy automatically! const shapeBuffer = this.device.createBuffer({ mappedAtCreation: true, size: shape.byteLength, @@ -270,7 +341,10 @@ export class TextureOp { passEncoder.setBindGroup(0, bindGroup); passEncoder.dispatch( - dispatchX / workGroupSize[0] / workPerThread, + Math.ceil(dispatchX / workGroupSize[0] / workPerThread), + Math.ceil(dispatchY / workGroupSize[1] / workPerThread)); + console.log( + 'd x y ' + dispatchX / workGroupSize[0] / workPerThread + ', ' + dispatchY / workGroupSize[1] / workPerThread); passEncoder.endPass(); // Submit GPU commands. @@ -283,13 +357,50 @@ export class TextureOp { return new Float32Array(arrayBuffer); } + /* + PackTextureData(const RGBA8* srcData, width, height, + srcTexelsPerRow, RGBA8* dstData, dstTexelsPerRow) { + for ( + int y = 0; y < height; ++y) { for ( int x = 0; x < width; ++x) { + int src = x + y * srcTexelsPerRow; + int dst = x + y * dstTexelsPerRow; + dstData[dst] = srcData[src]; + } + } + } + */ + + + // This will remove padding for data downloading from GPU texture. + private removeTexturePadding( + textureDataWithPadding: Float32Array, + width: number, + height: number, + bytesPerRow: number, + ) { + let textureData = new Float32Array(width * height); + console.log( + 'removeTexturePadding textureDataWithPadding =' + + textureDataWithPadding); + for (let y = 0; y < height; ++y) { + for (let x = 0; x < width; ++x) { + const src = x + y * bytesPerRow / this.kBytesPerTexel; + const dst = x + y * width; + textureData[dst] = textureDataWithPadding[src]; + } + } + console.log('removeTexturePadding textureData =' + textureData); + return textureData; + } + async getBufferData() { // Get a GPU buffer for reading in an unmapped state. const gpuReadBuffer = this.device.createBuffer({ - size: this.getBufferSize(), // Float32Array.BYTES_PER_ELEMENT * - // (this.shape[0] * this.shape[1]), + size: this.getBufferSizeRead(), // Float32Array.BYTES_PER_ELEMENT * + // (this.shape[0] * this.shape[1]), usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ }); + console.log('xx read createBuffer size=' + this.getBufferSizeRead()); // console.log('T2B this.getBufferSize()=' + this.getBufferSize()); // Commands submission. const commandEncoder = this.device.createCommandEncoder(); @@ -297,8 +408,11 @@ export class TextureOp { const [widthTex, heightTex] = tex_util.getPackedMatrixTextureShapeWidthHeight( this.shape[0], this.shape[1], this.format); - // console.log('widthTex = ' + widthTex + '; heightTex = ' + heightTex); const bytesPerRow = tex_util.getBytesPerRow(widthTex, this.kBytesPerTexel); + console.log( + 'xx copyTextureToBuffer: widthTex = ' + widthTex + + '; heightTex = ' + heightTex + ', bytesPerRow' + bytesPerRow + + ', this.getBufferSizeRead()=' + this.getBufferSizeRead()); // Encode commands for copying texture to buffer. commandEncoder.copyTextureToBuffer( { @@ -306,8 +420,15 @@ export class TextureOp { mipLevel: 0, origin: {x: 0, y: 0, z: 0} }, - {buffer: gpuReadBuffer, bytesPerRow: bytesPerRow}, + { + buffer: gpuReadBuffer, + bytesPerRow: bytesPerRow + // rowsPerImage: heightTex + }, {width: widthTex, height: heightTex, depth: 1}); + console.log( + 'xx read copyTextureToBuffer widthTex, heightTex=' + widthTex + ', ' + + heightTex); // Submit GPU commands. this.device.defaultQueue.submit([commandEncoder.finish()]); // t.expectContents(dst, data); @@ -329,7 +450,9 @@ export class TextureOp { // this.releaseBuffer(gpuReadBuffer); gpuReadBuffer.unmap(); gpuReadBuffer.destroy(); - return arrayBuffer; + return this.removeTexturePadding( + new Float32Array(arrayBuffer), widthTex, heightTex, bytesPerRow); + // return arrayBuffer; } // ---------Below code is not used!-------------------