1/* 2 * This file houses utilities for copying blocks of memory to and from 3 * the WASM heap. 4 */ 5 6/** 7 * Malloc returns a TypedArray backed by the C++ memory of the 8 * given length. It should only be used by advanced users who 9 * can manage memory and initialize values properly. When used 10 * correctly, it can save copying of data between JS and C++. 11 * When used incorrectly, it can lead to memory leaks. 12 * Any memory allocated by CanvasKit.Malloc needs to be released with CanvasKit.Free. 13 * 14 * const mObj = CanvasKit.Malloc(Float32Array, 20); 15 * Get a TypedArray view around the malloc'd memory (this does not copy anything). 16 * const ta = mObj.toTypedArray(); 17 * // store data into ta 18 * const cf = CanvasKit.ColorFilter.MakeMatrix(ta); // mObj could also be used. 19 * 20 * // eventually... 21 * CanvasKit.Free(mObj); 22 * 23 * @param {TypedArray} typedArray - constructor for the typedArray. 24 * @param {number} len - number of *elements* to store. 25 */ 26CanvasKit.Malloc = function(typedArray, len) { 27 var byteLen = len * typedArray.BYTES_PER_ELEMENT; 28 var ptr = CanvasKit._malloc(byteLen); 29 return { 30 '_ck': true, 31 'length': len, 32 'byteOffset': ptr, 33 typedArray: null, 34 'subarray': function(start, end) { 35 var sa = this['toTypedArray']().subarray(start, end); 36 sa['_ck'] = true; 37 return sa; 38 }, 39 'toTypedArray': function() { 40 // Check if the previously allocated array is still usable. 41 // If it's falsy, then we haven't created an array yet. 42 // If it's empty, then WASM resized memory and emptied the array. 43 if (this.typedArray && this.typedArray.length) { 44 return this.typedArray; 45 } 46 this.typedArray = new typedArray(CanvasKit.HEAPU8.buffer, ptr, len); 47 // add a marker that this was allocated in C++ land 48 this.typedArray['_ck'] = true; 49 return this.typedArray; 50 }, 51 }; 52}; 53 54/** 55 * Free frees the memory returned by Malloc. 56 * Any memory allocated by CanvasKit.Malloc needs to be released with CanvasKit.Free. 57 */ 58CanvasKit.Free = function(mallocObj) { 59 CanvasKit._free(mallocObj['byteOffset']); 60 mallocObj['byteOffset'] = nullptr; 61 // Set these to null to make sure the TypedArrays can be garbage collected. 62 mallocObj['toTypedArray'] = null; 63 mallocObj.typedArray = null; 64}; 65 66// This helper will free the given pointer unless the provided array is one 67// that was returned by CanvasKit.Malloc. 68function freeArraysThatAreNotMallocedByUsers(ptr, arr) { 69 if (!wasMalloced(arr)) { 70 CanvasKit._free(ptr); 71 } 72} 73 74// wasMalloced returns true if the object was created by a call to Malloc. This is determined 75// by looking at a property that was added to our Malloc obj and typed arrays. 76function wasMalloced(obj) { 77 return obj && obj['_ck']; 78} 79 80// We define some "scratch" variables which will house both the pointer to 81// memory we allocate at startup as well as a Malloc object, which we can 82// use to get a TypedArray view of that memory. 83 84var _scratch3x3MatrixPtr = nullptr; 85var _scratch3x3Matrix; // the result from CanvasKit.Malloc 86 87var _scratch4x4MatrixPtr = nullptr; 88var _scratch4x4Matrix; 89 90var _scratchColorPtr = nullptr; 91var _scratchColor; 92 93var _scratchFourFloatsA; 94var _scratchFourFloatsAPtr = nullptr; 95 96var _scratchFourFloatsB; 97var _scratchFourFloatsBPtr = nullptr; 98 99var _scratchThreeFloatsA; 100var _scratchThreeFloatsAPtr = nullptr; 101 102var _scratchThreeFloatsB; 103var _scratchThreeFloatsBPtr = nullptr; 104 105var _scratchIRect; 106var _scratchIRectPtr = nullptr; 107 108var _scratchRRect; 109var _scratchRRectPtr = nullptr; 110 111var _scratchRRect2; 112var _scratchRRect2Ptr = nullptr; 113 114// arr can be a normal JS array or a TypedArray 115// dest is a string like 'HEAPU32' that specifies the type the src array 116// should be copied into. 117// ptr can be optionally provided if the memory was already allocated. 118// Callers should eventually free the data unless the C++ object owns the memory, 119// or the provided pointer is a scratch pointer or a user-malloced value. 120// see also freeArraysThatAreNotMallocedByUsers(). 121function copy1dArray(arr, dest, ptr) { 122 if (!arr || !arr.length) { 123 return nullptr; 124 } 125 // This was created with CanvasKit.Malloc, so it's already been copied. 126 if (wasMalloced(arr)) { 127 return arr.byteOffset; 128 } 129 var bytesPerElement = CanvasKit[dest].BYTES_PER_ELEMENT; 130 if (!ptr) { 131 ptr = CanvasKit._malloc(arr.length * bytesPerElement); 132 } 133 // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single 134 // byte elements. When we run _malloc, we always get an offset/pointer into 135 // that block of memory. 136 // CanvasKit exposes some different views to make it easier to work with 137 // different types. HEAPF32 for example, exposes it as a float* 138 // However, to make the ptr line up, we have to do some pointer arithmetic. 139 // Concretely, we need to convert ptr to go from an index into a 1-byte-wide 140 // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32) 141 // and thus we divide ptr by 4. 142 // It is important to make sure we are grabbing the freshest view of the 143 // memory possible because if we call _malloc and the heap needs to grow, 144 // the TypedArrayView will no longer be valid. 145 CanvasKit[dest].set(arr, ptr / bytesPerElement); 146 return ptr; 147} 148 149// Copies an array of colors to wasm, returning an object with the pointer 150// and info necessary to use the copied colors. 151// Accepts either a flat Float32Array, flat Uint32Array or Array of Float32Arrays. 152// If color is an object that was allocated with CanvasKit.Malloc, its pointer is 153// returned and no extra copy is performed. 154// TODO(nifong): have this accept color builders. 155function copyFlexibleColorArray(colors) { 156 var result = { 157 colorPtr: nullptr, 158 count: colors.length, 159 colorType: CanvasKit.ColorType.RGBA_F32, 160 }; 161 if (colors instanceof Float32Array) { 162 result.colorPtr = copy1dArray(colors, 'HEAPF32'); 163 result.count = colors.length / 4; 164 165 } else if (colors instanceof Uint32Array) { 166 result.colorPtr = copy1dArray(colors, 'HEAPU32'); 167 result.colorType = CanvasKit.ColorType.RGBA_8888; 168 169 } else if (colors instanceof Array) { 170 result.colorPtr = copyColorArray(colors); 171 } else { 172 throw('Invalid argument to copyFlexibleColorArray, Not a color array '+typeof(colors)); 173 } 174 return result; 175} 176 177function copyColorArray(arr) { 178 if (!arr || !arr.length) { 179 return nullptr; 180 } 181 // 4 floats per color, 4 bytes per float. 182 var ptr = CanvasKit._malloc(arr.length * 4 * 4); 183 184 var idx = 0; 185 var adjustedPtr = ptr / 4; // cast the byte pointer into a float pointer. 186 for (var r = 0; r < arr.length; r++) { 187 for (var c = 0; c < 4; c++) { 188 CanvasKit.HEAPF32[adjustedPtr + idx] = arr[r][c]; 189 idx++; 190 } 191 } 192 return ptr; 193} 194 195var defaultPerspective = Float32Array.of(0, 0, 1); 196 197// Copies the given DOMMatrix/Array/TypedArray to the CanvasKit heap and 198// returns a pointer to the memory. This memory is a float* of length 9. 199// If the passed in matrix is null/undefined, we return 0 (nullptr). The 200// returned pointer should NOT be freed, as it is either null or a scratch 201// pointer. 202function copy3x3MatrixToWasm(matr) { 203 if (!matr) { 204 return nullptr; 205 } 206 207 var wasm3x3Matrix = _scratch3x3Matrix['toTypedArray'](); 208 if (matr.length) { 209 if (matr.length === 6 || matr.length === 9) { 210 // matr should be an array or typed array. 211 copy1dArray(matr, 'HEAPF32', _scratch3x3MatrixPtr); 212 if (matr.length === 6) { 213 // Overwrite the last 3 floats with the default perspective. The divide 214 // by 4 casts the pointer into a float pointer. 215 CanvasKit.HEAPF32.set(defaultPerspective, 6 + _scratch3x3MatrixPtr / 4); 216 } 217 return _scratch3x3MatrixPtr; 218 } else if (matr.length === 16) { 219 // Downsample the 4x4 matrix into a 3x3 220 wasm3x3Matrix[0] = matr[0]; 221 wasm3x3Matrix[1] = matr[1]; 222 wasm3x3Matrix[2] = matr[3]; 223 224 wasm3x3Matrix[3] = matr[4]; 225 wasm3x3Matrix[4] = matr[5]; 226 wasm3x3Matrix[5] = matr[7]; 227 228 wasm3x3Matrix[6] = matr[12]; 229 wasm3x3Matrix[7] = matr[13]; 230 wasm3x3Matrix[8] = matr[15]; 231 return _scratch3x3MatrixPtr; 232 } 233 throw 'invalid matrix size'; 234 } else if (matr['m11'] === undefined) { 235 throw 'invalid matrix argument'; 236 } 237 // Reminder that DOMMatrix is column-major. 238 wasm3x3Matrix[0] = matr['m11']; 239 wasm3x3Matrix[1] = matr['m21']; 240 wasm3x3Matrix[2] = matr['m41']; 241 242 wasm3x3Matrix[3] = matr['m12']; 243 wasm3x3Matrix[4] = matr['m22']; 244 wasm3x3Matrix[5] = matr['m42']; 245 246 wasm3x3Matrix[6] = matr['m14']; 247 wasm3x3Matrix[7] = matr['m24']; 248 wasm3x3Matrix[8] = matr['m44']; 249 return _scratch3x3MatrixPtr; 250} 251 252 253// Copies the given DOMMatrix/Array/TypedArray to the CanvasKit heap and 254// returns a pointer to the memory. This memory is a float* of length 16. 255// If the passed in matrix is null/undefined, we return 0 (nullptr). The 256// returned pointer should NOT be freed, as it is either null or a scratch 257// pointer. 258function copy4x4MatrixToWasm(matr) { 259 if (!matr) { 260 return nullptr; 261 } 262 var wasm4x4Matrix = _scratch4x4Matrix['toTypedArray'](); 263 if (matr.length) { 264 if (matr.length !== 16 && matr.length !== 6 && matr.length !== 9) { 265 throw 'invalid matrix size'; 266 } 267 if (matr.length === 16) { 268 // matr should be an array or typed array. 269 return copy1dArray(matr, 'HEAPF32', _scratch4x4MatrixPtr); 270 } 271 // Upscale the row-major 3x3 or 3x2 matrix into a 4x4 row-major matrix 272 // TODO(skbug.com/10108) This will need to change when we convert our 273 // JS 4x4 to be column-major. 274 // When upscaling, we need to overwrite the 3rd column and the 3rd row with 275 // 0s. It's easiest to just do that with a fill command. 276 wasm4x4Matrix.fill(0); 277 wasm4x4Matrix[0] = matr[0]; 278 wasm4x4Matrix[1] = matr[1]; 279 // skip col 2 280 wasm4x4Matrix[3] = matr[2]; 281 282 wasm4x4Matrix[4] = matr[3]; 283 wasm4x4Matrix[5] = matr[4]; 284 // skip col 2 285 wasm4x4Matrix[7] = matr[5]; 286 287 // row2 == identity 288 wasm4x4Matrix[10] = 1; 289 290 wasm4x4Matrix[12] = matr[6]; 291 wasm4x4Matrix[13] = matr[7]; 292 // skip col 2 293 wasm4x4Matrix[15] = matr[8]; 294 295 if (matr.length === 6) { 296 // fix perspective for the 3x2 case (from above, they will be undefined). 297 wasm4x4Matrix[12]=0; 298 wasm4x4Matrix[13]=0; 299 wasm4x4Matrix[15]=1; 300 } 301 return _scratch4x4MatrixPtr; 302 } else if (matr['m11'] === undefined) { 303 throw 'invalid matrix argument'; 304 } 305 // Reminder that DOMMatrix is column-major. 306 wasm4x4Matrix[0] = matr['m11']; 307 wasm4x4Matrix[1] = matr['m21']; 308 wasm4x4Matrix[2] = matr['m31']; 309 wasm4x4Matrix[3] = matr['m41']; 310 311 wasm4x4Matrix[4] = matr['m12']; 312 wasm4x4Matrix[5] = matr['m22']; 313 wasm4x4Matrix[6] = matr['m32']; 314 wasm4x4Matrix[7] = matr['m42']; 315 316 wasm4x4Matrix[8] = matr['m13']; 317 wasm4x4Matrix[9] = matr['m23']; 318 wasm4x4Matrix[10] = matr['m33']; 319 wasm4x4Matrix[11] = matr['m43']; 320 321 wasm4x4Matrix[12] = matr['m14']; 322 wasm4x4Matrix[13] = matr['m24']; 323 wasm4x4Matrix[14] = matr['m34']; 324 wasm4x4Matrix[15] = matr['m44']; 325 return _scratch4x4MatrixPtr; 326} 327 328// copies a 4x4 matrix at the given pointer into a JS array. 329function copy4x4MatrixFromWasm(matrPtr) { 330 // read them out into an array. TODO(kjlubick): If we change Matrix to be 331 // typedArrays, then we should return a typed array here too. 332 var rv = new Array(16); 333 for (var i = 0; i < 16; i++) { 334 rv[i] = CanvasKit.HEAPF32[matrPtr/4 + i]; // divide by 4 to cast to float. 335 } 336 return rv; 337} 338 339// copies the given floats into the wasm heap as an SkColor4f. Unless a non-scratch pointer is 340// passed into ptr, callers do NOT need to free the returned pointer. 341function copyColorToWasm(color4f, ptr) { 342 return copy1dArray(color4f, 'HEAPF32', ptr || _scratchColorPtr); 343} 344 345// copies the given color into the wasm heap. Callers do not need to free the returned pointer. 346function copyColorComponentsToWasm(r, g, b, a) { 347 var colors = _scratchColor['toTypedArray'](); 348 colors[0] = r; 349 colors[1] = g; 350 colors[2] = b; 351 colors[3] = a; 352 return _scratchColorPtr; 353} 354 355// copies the given color into the wasm heap. Callers must free the returned pointer. 356function copyColorToWasmNoScratch(color4f) { 357 // TODO(kjlubick): accept 4 floats or int color 358 return copy1dArray(color4f, 'HEAPF32'); 359} 360 361// copies the four floats at the given pointer in a js Float32Array 362function copyColorFromWasm(colorPtr) { 363 var rv = new Float32Array(4); 364 for (var i = 0; i < 4; i++) { 365 rv[i] = CanvasKit.HEAPF32[colorPtr/4 + i]; // divide by 4 to cast to float. 366 } 367 return rv; 368} 369 370// copies the given floats into the wasm heap as an SkRect. Unless a non-scratch pointer is 371// passed into ptr, callers do NOT need to free the returned pointer. 372function copyRectToWasm(fourFloats, ptr) { 373 return copy1dArray(fourFloats, 'HEAPF32', ptr || _scratchFourFloatsAPtr); 374} 375 376// copies the given ints into the wasm heap as an SkIRect. Unless a non-scratch pointer is 377// passed into ptr, callers do NOT need to free the returned pointer. 378function copyIRectToWasm(fourInts, ptr) { 379 return copy1dArray(fourInts, 'HEAP32', ptr || _scratchIRectPtr); 380} 381 382// copies the four ints at the given pointer into a JS Int32Array 383function copyIRectFromWasm(rectMalloc, outputArray) { 384 var ta = rectMalloc['toTypedArray'](); 385 if (outputArray) { 386 outputArray.set(ta); 387 return outputArray; 388 } 389 return ta.slice(); 390} 391 392// copies the given floats into the wasm heap as an SkRRect. Unless a non-scratch pointer is 393// passed into ptr, callers do NOT need to free the returned pointer. 394function copyRRectToWasm(twelveFloats, ptr) { 395 return copy1dArray(twelveFloats, 'HEAPF32', ptr || _scratchRRectPtr); 396} 397