constructing TypedArray from another TypedArray is slow

https://bugs.webkit.org/show_bug.cgi?id=90838

Patch by Arnaud Renevier <a.renevier@sisa.samsung.com> on 2012-07-26
Reviewed by Kenneth Russell.

PerformanceTests:

* Bindings/typed-array-construct-from-same-type.html: Added.
* Bindings/typed-array-construct-from-typed.html: Added.

Source/WebCore:

When constructing a typed array from an array like element, try to
determine if the argument is a typed array. If so, cast the argument
to a typed array, and read each element with .item() method. That
avoid reading the value as a JSValue, and speedups construction by
approximatively 3x (even 30x if TypedArrays are both the same type).

In order to achieve that, we use virtual getType method. We can use
this information to cast the TypedArray to the actual type, and then
read the values from the source.

Introduce constructArrayBufferViewWithTypedArrayArgument template
function which returns a new typed array if first argument is a typed
array, or 0 otherwise.

This patch also replaces previous is<Type>Array() calls with new
getType method.

* bindings/js/JSArrayBufferViewHelper.h:
(WebCore::constructArrayBufferViewWithTypedArrayArgument):
(WebCore):
(WebCore::constructArrayBufferView):
* bindings/v8/SerializedScriptValue.cpp:
* html/canvas/DataView.h:
(DataView):
(WebCore::DataView::getType):
* html/canvas/WebGLRenderingContext.cpp:
(WebCore):
(WebCore::WebGLRenderingContext::readPixels):
(WebCore::WebGLRenderingContext::validateTexFuncData):
* page/Crypto.cpp:

Source/WTF:

Introduce virtual method getType on ArrayBufferView. It returns the actual
type of the view. This method replaces previous is<Type>Array() methods.

* wtf/ArrayBufferView.h:
* wtf/Float32Array.h:
(WTF::Float32Array::getType):
(Float32Array):
* wtf/Float64Array.h:
(WTF::Float64Array::getType):
(Float64Array):
* wtf/Int16Array.h:
(WTF::Int16Array::getType):
(Int16Array):
* wtf/Int32Array.h:
(WTF::Int32Array::getType):
(Int32Array):
* wtf/Int8Array.h:
(WTF::Int8Array::getType):
(Int8Array):
* wtf/IntegralTypedArrayBase.h:
* wtf/TypedArrayBase.h:
(TypedArrayBase):
(WTF::TypedArrayBase::item):
* wtf/Uint16Array.h:
(WTF::Uint16Array::getType):
(Uint16Array):
* wtf/Uint32Array.h:
(WTF::Uint32Array::getType):
(Uint32Array):
* wtf/Uint8Array.h:
(WTF::Uint8Array::getType):
(Uint8Array):
* wtf/Uint8ClampedArray.h:
(WTF::Uint8ClampedArray::getType):
(Uint8ClampedArray):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@123819 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent c58e1d66
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<script>
var length = 10000000;
var source = new Uint8Array(length);
for (var i = 0; i < length; i++) {
source[i] = i;
}
PerfTestRunner.run(function() {
var target = new Uint8Array(source);
});
</script>
</body>
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<script>
var length = 10000000;
var source = new Uint8Array(length);
for (var i = 0; i < length; i++) {
source[i] = i;
}
PerfTestRunner.run(function() {
var target = new Float64Array(source);
});
</script>
</body>
2012-07-26 Arnaud Renevier <a.renevier@sisa.samsung.com>
constructing TypedArray from another TypedArray is slow
https://bugs.webkit.org/show_bug.cgi?id=90838
Reviewed by Kenneth Russell.
* Bindings/typed-array-construct-from-same-type.html: Added.
* Bindings/typed-array-construct-from-typed.html: Added.
2012-07-25 Ryosuke Niwa <rniwa@webkit.org>
Import more replay performance tests from Mozilla's Tp5 test suite
......
2012-07-26 Arnaud Renevier <a.renevier@sisa.samsung.com>
constructing TypedArray from another TypedArray is slow
https://bugs.webkit.org/show_bug.cgi?id=90838
Reviewed by Kenneth Russell.
Introduce virtual method getType on ArrayBufferView. It returns the actual
type of the view. This method replaces previous is<Type>Array() methods.
* wtf/ArrayBufferView.h:
* wtf/Float32Array.h:
(WTF::Float32Array::getType):
(Float32Array):
* wtf/Float64Array.h:
(WTF::Float64Array::getType):
(Float64Array):
* wtf/Int16Array.h:
(WTF::Int16Array::getType):
(Int16Array):
* wtf/Int32Array.h:
(WTF::Int32Array::getType):
(Int32Array):
* wtf/Int8Array.h:
(WTF::Int8Array::getType):
(Int8Array):
* wtf/IntegralTypedArrayBase.h:
* wtf/TypedArrayBase.h:
(TypedArrayBase):
(WTF::TypedArrayBase::item):
* wtf/Uint16Array.h:
(WTF::Uint16Array::getType):
(Uint16Array):
* wtf/Uint32Array.h:
(WTF::Uint32Array::getType):
(Uint32Array):
* wtf/Uint8Array.h:
(WTF::Uint8Array::getType):
(Uint8Array):
* wtf/Uint8ClampedArray.h:
(WTF::Uint8ClampedArray::getType):
(Uint8ClampedArray):
2012-07-26 Zeno Albisser <zeno@webkit.org>
[Qt] requestAnimationFrame should only trigger when a new frame can be displayed.
......
......@@ -38,16 +38,19 @@ namespace WTF {
class WTF_EXPORT_PRIVATE_RTTI ArrayBufferView : public RefCounted<ArrayBufferView> {
public:
virtual bool isByteArray() const { return false; }
virtual bool isUnsignedByteArray() const { return false; }
virtual bool isUnsignedByteClampedArray() const { return false; }
virtual bool isShortArray() const { return false; }
virtual bool isUnsignedShortArray() const { return false; }
virtual bool isIntArray() const { return false; }
virtual bool isUnsignedIntArray() const { return false; }
virtual bool isFloatArray() const { return false; }
virtual bool isDoubleArray() const { return false; }
virtual bool isDataView() const { return false; }
enum ViewType {
TypeInt8,
TypeUint8,
TypeUint8Clamped,
TypeInt16,
TypeUint16,
TypeInt32,
TypeUint32,
TypeFloat32,
TypeFloat64,
TypeDataView
};
virtual ViewType getType() const = 0;
PassRefPtr<ArrayBuffer> buffer() const
{
......
......@@ -48,27 +48,20 @@ public:
TypedArrayBase<float>::data()[index] = static_cast<float>(value);
}
// Invoked by the indexed getter. Does not perform range checks; caller
// is responsible for doing so and returning undefined as necessary.
float item(unsigned index) const
{
ASSERT(index < TypedArrayBase<float>::m_length);
float result = TypedArrayBase<float>::data()[index];
return result;
}
inline PassRefPtr<Float32Array> subarray(int start) const;
inline PassRefPtr<Float32Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeFloat32;
}
private:
inline Float32Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<float>;
// Overridden from ArrayBufferView.
virtual bool isFloatArray() const { return true; }
};
PassRefPtr<Float32Array> Float32Array::create(unsigned length)
......
......@@ -48,27 +48,20 @@ public:
TypedArrayBase<double>::data()[index] = static_cast<double>(value);
}
// Invoked by the indexed getter. Does not perform range checks; caller
// is responsible for doing so and returning undefined as necessary.
double item(unsigned index) const
{
ASSERT(index < TypedArrayBase<double>::m_length);
double result = TypedArrayBase<double>::data()[index];
return result;
}
inline PassRefPtr<Float64Array> subarray(int start) const;
inline PassRefPtr<Float64Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeFloat64;
}
private:
inline Float64Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<double>;
// Overridden from ArrayBufferView.
virtual bool isDoubleArray() const { return true; }
};
PassRefPtr<Float64Array> Float64Array::create(unsigned length)
......
......@@ -45,15 +45,17 @@ public:
inline PassRefPtr<Int16Array> subarray(int start) const;
inline PassRefPtr<Int16Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeInt16;
}
private:
inline Int16Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<short>;
// Overridden from ArrayBufferView.
virtual bool isShortArray() const { return true; }
};
PassRefPtr<Int16Array> Int16Array::create(unsigned length)
......
......@@ -44,15 +44,17 @@ public:
inline PassRefPtr<Int32Array> subarray(int start) const;
inline PassRefPtr<Int32Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeInt32;
}
private:
inline Int32Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<int>;
// Overridden from ArrayBufferView.
virtual bool isIntArray() const { return true; }
};
PassRefPtr<Int32Array> Int32Array::create(unsigned length)
......
......@@ -46,15 +46,17 @@ public:
inline PassRefPtr<Int8Array> subarray(int start) const;
inline PassRefPtr<Int8Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeInt8;
}
private:
inline Int8Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<signed char>;
// Overridden from ArrayBufferView.
virtual bool isByteArray() const { return true; }
};
PassRefPtr<Int8Array> Int8Array::create(unsigned length)
......
......@@ -50,14 +50,6 @@ class IntegralTypedArrayBase : public TypedArrayBase<T> {
TypedArrayBase<T>::data()[index] = static_cast<T>(static_cast<int64_t>(value));
}
// Invoked by the indexed getter. Does not perform range checks; caller
// is responsible for doing so and returning undefined as necessary.
T item(unsigned index) const
{
ASSERT(index < TypedArrayBase<T>::m_length);
return TypedArrayBase<T>::data()[index];
}
protected:
IntegralTypedArrayBase(PassRefPtr<ArrayBuffer> buffer, unsigned byteOffset, unsigned length)
: TypedArrayBase<T>(buffer, byteOffset, length)
......
......@@ -65,6 +65,14 @@ class TypedArrayBase : public ArrayBufferView {
return m_length * sizeof(T);
}
// Invoked by the indexed getter. Does not perform range checks; caller
// is responsible for doing so and returning undefined as necessary.
T item(unsigned index) const
{
ASSERT(index < TypedArrayBase<T>::m_length);
return TypedArrayBase<T>::data()[index];
}
protected:
TypedArrayBase(PassRefPtr<ArrayBuffer> buffer, unsigned byteOffset, unsigned length)
: ArrayBufferView(buffer, byteOffset)
......
......@@ -46,15 +46,17 @@ public:
inline PassRefPtr<Uint16Array> subarray(int start) const;
inline PassRefPtr<Uint16Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeUint16;
}
private:
inline Uint16Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<unsigned short>;
// Overridden from ArrayBufferView.
virtual bool isUnsignedShortArray() const { return true; }
};
PassRefPtr<Uint16Array> Uint16Array::create(unsigned length)
......
......@@ -46,15 +46,17 @@ public:
inline PassRefPtr<Uint32Array> subarray(int start) const;
inline PassRefPtr<Uint32Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeUint32;
}
private:
inline Uint32Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<unsigned int>;
// Overridden from ArrayBufferView.
virtual bool isUnsignedIntArray() const { return true; }
};
PassRefPtr<Uint32Array> Uint32Array::create(unsigned length)
......
......@@ -46,15 +46,17 @@ public:
inline PassRefPtr<Uint8Array> subarray(int start) const;
inline PassRefPtr<Uint8Array> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeUint8;
}
protected:
inline Uint8Array(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<unsigned char>;
// Overridden from ArrayBufferView.
virtual bool isUnsignedByteArray() const { return true; }
};
PassRefPtr<Uint8Array> Uint8Array::create(unsigned length)
......
......@@ -55,15 +55,17 @@ public:
inline PassRefPtr<Uint8ClampedArray> subarray(int start) const;
inline PassRefPtr<Uint8ClampedArray> subarray(int start, int end) const;
virtual ViewType getType() const
{
return TypeUint8Clamped;
}
private:
inline Uint8ClampedArray(PassRefPtr<ArrayBuffer>,
unsigned byteOffset,
unsigned length);
// Make constructor visible to superclass.
friend class TypedArrayBase<unsigned char>;
// Overridden from ArrayBufferView.
virtual bool isUnsignedByteClampedArray() const { return true; }
};
PassRefPtr<Uint8ClampedArray> Uint8ClampedArray::create(unsigned length)
......
2012-07-26 Arnaud Renevier <a.renevier@sisa.samsung.com>
constructing TypedArray from another TypedArray is slow
https://bugs.webkit.org/show_bug.cgi?id=90838
Reviewed by Kenneth Russell.
When constructing a typed array from an array like element, try to
determine if the argument is a typed array. If so, cast the argument
to a typed array, and read each element with .item() method. That
avoid reading the value as a JSValue, and speedups construction by
approximatively 3x (even 30x if TypedArrays are both the same type).
In order to achieve that, we use virtual getType method. We can use
this information to cast the TypedArray to the actual type, and then
read the values from the source.
Introduce constructArrayBufferViewWithTypedArrayArgument template
function which returns a new typed array if first argument is a typed
array, or 0 otherwise.
This patch also replaces previous is<Type>Array() calls with new
getType method.
* bindings/js/JSArrayBufferViewHelper.h:
(WebCore::constructArrayBufferViewWithTypedArrayArgument):
(WebCore):
(WebCore::constructArrayBufferView):
* bindings/v8/SerializedScriptValue.cpp:
* html/canvas/DataView.h:
(DataView):
(WebCore::DataView::getType):
* html/canvas/WebGLRenderingContext.cpp:
(WebCore):
(WebCore::WebGLRenderingContext::readPixels):
(WebCore::WebGLRenderingContext::validateTexFuncData):
* page/Crypto.cpp:
2012-07-26 Max Vujovic <mvujovic@adobe.com>
Added binding and updated chromium tests.
......@@ -29,6 +29,7 @@
#include "ExceptionCode.h"
#include "JSArrayBuffer.h"
#include "JSArrayBufferView.h"
#include "JSDOMBinding.h"
#include <interpreter/CallFrame.h>
#include <runtime/ArgList.h>
......@@ -36,6 +37,7 @@
#include <runtime/JSObject.h>
#include <runtime/JSValue.h>
#include <wtf/ArrayBufferView.h>
#include <wtf/TypedArrayBase.h>
namespace WebCore {
......@@ -83,6 +85,72 @@ JSC::JSValue setWebGLArrayHelper(JSC::ExecState* exec, T* impl, T* (*conversionF
return JSC::throwSyntaxError(exec);
}
// Template function used by XXXArrayConstructors.
// If this returns 0, it will already have thrown a JavaScript exception.
template<class C, typename T>
PassRefPtr<C> constructArrayBufferViewWithTypedArrayArgument(JSC::ExecState* exec)
{
RefPtr<ArrayBufferView> source = toArrayBufferView(exec->argument(0));
if (!source)
return 0;
ArrayBufferView::ViewType sourceType = source->getType();
if (sourceType == ArrayBufferView::TypeDataView)
return 0;
uint32_t length = asObject(exec->argument(0))->get(exec, JSC::Identifier(exec, "length")).toUInt32(exec);
RefPtr<C> array = C::create(length);
if (!array) {
setDOMException(exec, INDEX_SIZE_ERR);
return array;
}
if (array->getType() == sourceType) {
memcpy(array->baseAddress(), source->baseAddress(), length * sizeof(T));
return array;
}
switch (sourceType) {
case ArrayBufferView::TypeInt8:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<signed char> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeUint8:
case ArrayBufferView::TypeUint8Clamped:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<unsigned char> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeInt16:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<signed short> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeUint16:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<unsigned short> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeInt32:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<int> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeUint32:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<unsigned int> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeFloat32:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<float> *>(source.get())->item(i)));
break;
case ArrayBufferView::TypeFloat64:
for (unsigned i = 0; i < length; ++i)
array->set(i, (T)(static_cast<TypedArrayBase<double> *>(source.get())->item(i)));
break;
default:
return 0;
}
return array;
}
// Template function used by XXXArrayConstructors.
// If this returns 0, it will already have thrown a JavaScript exception.
template<class C, typename T>
......@@ -138,6 +206,10 @@ PassRefPtr<C> constructArrayBufferView(JSC::ExecState* exec)
if (view)
return view;
view = constructArrayBufferViewWithTypedArrayArgument<C, T>(exec);
if (view)
return view;
JSC::JSObject* srcArray = asObject(exec->argument(0));
uint32_t length = srcArray->get(exec, JSC::Identifier(exec, "length")).toUInt32(exec);
RefPtr<C> array = C::create(length);
......
......@@ -412,25 +412,27 @@ public:
ASSERT(static_cast<const uint8_t*>(arrayBuffer.data()) + arrayBufferView.byteOffset() ==
static_cast<const uint8_t*>(arrayBufferView.baseAddress()));
#endif
if (arrayBufferView.isByteArray())
ArrayBufferView::ViewType type = arrayBufferView.getType();
if (type == ArrayBufferView::TypeInt8)
append(ByteArrayTag);
else if (arrayBufferView.isUnsignedByteClampedArray())
else if (type == ArrayBufferView::TypeUint8Clamped)
append(UnsignedByteClampedArrayTag);
else if (arrayBufferView.isUnsignedByteArray())
else if (type == ArrayBufferView::TypeUint8)
append(UnsignedByteArrayTag);
else if (arrayBufferView.isShortArray())
else if (type == ArrayBufferView::TypeInt16)
append(ShortArrayTag);
else if (arrayBufferView.isUnsignedShortArray())
else if (type == ArrayBufferView::TypeUint16)
append(UnsignedShortArrayTag);
else if (arrayBufferView.isIntArray())
else if (type == ArrayBufferView::TypeInt32)
append(IntArrayTag);
else if (arrayBufferView.isUnsignedIntArray())
else if (type == ArrayBufferView::TypeUint32)
append(UnsignedIntArrayTag);
else if (arrayBufferView.isFloatArray())
else if (type == ArrayBufferView::TypeFloat32)
append(FloatArrayTag);
else if (arrayBufferView.isDoubleArray())
else if (type == ArrayBufferView::TypeFloat64)
append(DoubleArrayTag);
else if (arrayBufferView.isDataView())
else if (type == ArrayBufferView::TypeDataView)
append(DataViewTag);
else
ASSERT_NOT_REACHED();
......
......@@ -38,7 +38,6 @@ public:
static PassRefPtr<DataView> create(unsigned length);
static PassRefPtr<DataView> create(PassRefPtr<ArrayBuffer>, unsigned byteOffset, unsigned byteLength);
virtual bool isDataView() const { return true; }
virtual unsigned length() const { return m_byteLength; }
virtual unsigned byteLength() const { return m_byteLength; }
virtual PassRefPtr<ArrayBufferView> slice(int, int) const { return 0; }
......@@ -73,6 +72,11 @@ public:
void setFloat64(unsigned byteOffset, double value, ExceptionCode& ec) { setFloat64(byteOffset, value, false, ec); }
void setFloat64(unsigned byteOffset, double value, bool littleEndian, ExceptionCode&);
virtual ViewType getType() const
{
return TypeDataView;
}
protected:
virtual void neuter();
......
......@@ -3280,7 +3280,7 @@ void WebGLRenderingContext::readPixels(GC3Dint x, GC3Dint y, GC3Dsizei width, GC
return;
}
// Validate array type against pixel type.
if (!pixels->isUnsignedByteArray()) {
if (pixels->getType() != ArrayBufferView::TypeUint8) {
synthesizeGLError(GraphicsContext3D::INVALID_OPERATION, "readPixels", "ArrayBufferView not Uint8Array");
return;
}
......@@ -4937,7 +4937,7 @@ bool WebGLRenderingContext::validateTexFuncData(const char* functionName, GC3Din
switch (type) {
case GraphicsContext3D::UNSIGNED_BYTE:
if (!pixels->isUnsignedByteArray()) {
if (pixels->getType() != ArrayBufferView::TypeUint8) {
synthesizeGLError(GraphicsContext3D::INVALID_OPERATION, functionName, "type UNSIGNED_BYTE but ArrayBufferView not Uint8Array");
return false;
}
......@@ -4945,13 +4945,13 @@ bool WebGLRenderingContext::validateTexFuncData(const char* functionName, GC3Din
case GraphicsContext3D::UNSIGNED_SHORT_5_6_5:
case GraphicsContext3D::UNSIGNED_SHORT_4_4_4_4:
case GraphicsContext3D::UNSIGNED_SHORT_5_5_5_1:
if (!pixels->isUnsignedShortArray()) {
if (pixels->getType() != ArrayBufferView::TypeUint16) {
synthesizeGLError(GraphicsContext3D::INVALID_OPERATION, functionName, "type UNSIGNED_SHORT but ArrayBufferView not Uint16Array");
return false;
}
break;
case GraphicsContext3D::FLOAT: // OES_texture_float
if (!pixels->isFloatArray()) {
if (pixels->getType() != ArrayBufferView::TypeFloat32) {
synthesizeGLError(GraphicsContext3D::INVALID_OPERATION, functionName, "type FLOAT but ArrayBufferView not Float32Array");
return false;
}
......
......@@ -31,7 +31,7 @@
#include "Crypto.h"
#include "ExceptionCode.h"
#include <wtf/Uint8Array.h>
#include <wtf/ArrayBufferView.h>
#include <wtf/CryptographicallyRandomNumber.h>
namespace WebCore {
......@@ -40,13 +40,14 @@ namespace {
bool isIntegerArray(ArrayBufferView* array)
{
return array->isByteArray()
|| array->isUnsignedByteArray()
|| array->isUnsignedByteClampedArray()
|| array->isShortArray()
|| array->isUnsignedShortArray()
|| array->isIntArray()
|| array->isUnsignedIntArray();
ArrayBufferView::ViewType type = array->getType();
return type == ArrayBufferView::TypeInt8
|| type == ArrayBufferView::TypeUint8
|| type == ArrayBufferView::TypeUint8Clamped
|| type == ArrayBufferView::TypeInt16
|| type == ArrayBufferView::TypeUint16
|| type == ArrayBufferView::TypeInt32
|| type == ArrayBufferView::TypeUint32;
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment