Skip to content

Commit

Permalink
Merge branch 'typed-array-clusterfuck'
Browse files Browse the repository at this point in the history
  • Loading branch information
phoboslab committed Dec 12, 2015
2 parents 84d342c + cc5a057 commit 08741b4
Show file tree
Hide file tree
Showing 50 changed files with 588 additions and 7,704 deletions.
58 changes: 20 additions & 38 deletions Ejecta.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# About The typed-array-clusterfuck Branch

This branch of Ejecta uses the JavaScriptCore library provided by the system. This mainly means two things: The resulting binary will be much smaller and Typed Arrays are extremely slow.

On 32bit systems reading/writing large Typed Arrays will be pretty much unusably slow. On 64bit systems it's highly optimized to take about 8ms for reading for 1MB of data and about 20ms for writing. This _may_ be ok for simple WebGL stuff or if you don't use g`et/setImageData()` on the Canvas2D context much.

This branch of Ejecta _may_ be AppStore compatible for tvOS. I'm still waiting on the review, but at least the binary upload didn't fail like with the main branch.


# Ejecta

Ejecta is a fast, open source JavaScript, Canvas & Audio implementation for iOS. Think of it as a Browser that can only display a Canvas element.
Expand Down
9 changes: 6 additions & 3 deletions Source/Ejecta/EJCanvas/2D/EJBindingImageData.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import "EJBindingImageData.h"
#import "EJJavaScriptView.h"
#import <JavaScriptCore/JSTypedArray.h>
#import "EJConvertTypedArray.h"

@implementation EJBindingImageData
@synthesize imageData;
Expand Down Expand Up @@ -28,7 +28,7 @@ - (EJImageData *)imageData {
int byteLength = imageData.width * imageData.height * 4;

[EJTexture
premultiplyPixels:JSObjectGetTypedArrayDataPtr(scriptView.jsGlobalContext, dataArray, NULL)
premultiplyPixels:JSObjectGetTypedArrayData(scriptView.jsGlobalContext, dataArray).bytes
to:imageData.pixels.mutableBytes
byteLength:byteLength format:GL_RGBA];
}
Expand All @@ -48,10 +48,13 @@ - (EJTexture *)texture {
dataArray = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeUint8ClampedArray, byteLength);
JSValueProtect(ctx, dataArray);

NSMutableData *unPremultiplied = [NSMutableData dataWithLength:byteLength];
[EJTexture
unPremultiplyPixels:imageData.pixels.bytes
to:JSObjectGetTypedArrayDataPtr(ctx, dataArray, NULL)
to:unPremultiplied.mutableBytes
byteLength:byteLength format:GL_RGBA];

JSObjectSetTypedArrayData(ctx, dataArray, unPremultiplied);
}
return dataArray;
}
Expand Down
81 changes: 50 additions & 31 deletions Source/Ejecta/EJCanvas/WebGL/EJBindingCanvasContextWebGL.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#import "EJConvertWebGL.h"
#import "EJJavaScriptView.h"

#import <JavaScriptCore/JSTypedArray.h>
#import "EJConvertTypedArray.h"


@implementation EJBindingCanvasContextWebGL
Expand Down Expand Up @@ -365,10 +365,10 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
glBufferData(target, psize, NULL, usage);
}
else if( JSValueIsObject(ctx, argv[1]) ) {
size_t size;
GLvoid *buffer = JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)argv[1], &size);
if( buffer ) {
glBufferData(target, size, buffer, usage);
NSData *data = JSObjectGetTypedArrayData(ctx, (JSObjectRef)argv[1]);
if( data ) {
const GLvoid *buffer = data.bytes;
glBufferData(target, data.length, buffer, usage);
}
}

Expand All @@ -383,10 +383,10 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
GLenum target = JSValueToNumberFast(ctx, argv[0]);
GLintptr offset = JSValueToNumberFast(ctx, argv[1]);

size_t size;
GLvoid *buffer = JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)argv[2], &size);
if( buffer ) {
glBufferSubData(target, offset, size, buffer);
NSData *data = JSObjectGetTypedArrayData(ctx, (JSObjectRef)argv[2]);
if( data ) {
const GLvoid *buffer = data.bytes;
glBufferSubData(target, offset, data.length, buffer);
}
return NULL;
}
Expand Down Expand Up @@ -726,6 +726,7 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
int intbuffer[4];
float floatvalue;
JSValueRef arrayArgs[4];
NSMutableData *data;

switch( pname ) {
// Float32Array (with 0 elements)
Expand All @@ -737,28 +738,40 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
case GL_ALIASED_LINE_WIDTH_RANGE:
case GL_ALIASED_POINT_SIZE_RANGE:
case GL_DEPTH_RANGE:
data = [[NSMutableData alloc] initWithLength:2 * sizeof(GLfloat)];
glGetFloatv(pname, data.mutableBytes);
ret = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeFloat32Array, 2);
glGetFloatv(pname, JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)ret, NULL));
JSObjectSetTypedArrayData(ctx, (JSObjectRef)ret, data);
[data release];
break;

// Float32Array (with 4 values)
case GL_BLEND_COLOR:
case GL_COLOR_CLEAR_VALUE:
data = [[NSMutableData alloc] initWithLength:4 * sizeof(GLfloat)];
glGetFloatv(pname, data.mutableBytes);
ret = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeFloat32Array, 4);
glGetFloatv(pname, JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)ret, NULL));
JSObjectSetTypedArrayData(ctx, (JSObjectRef)ret, data);
[data release];
break;

// Int32Array (with 2 values)
case GL_MAX_VIEWPORT_DIMS:
data = [[NSMutableData alloc] initWithLength:2 * sizeof(GLint)];
glGetIntegerv(pname, data.mutableBytes);
ret = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeInt32Array, 2);
glGetIntegerv(pname, JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)ret, NULL));
JSObjectSetTypedArrayData(ctx, (JSObjectRef)ret, data);
[data release];
break;

// Int32Array (with 4 values)
case GL_SCISSOR_BOX:
case GL_VIEWPORT:
data = [[NSMutableData alloc] initWithLength:4 * sizeof(GLint)];
glGetIntegerv(pname, data.mutableBytes);
ret = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeInt32Array, 4);
glGetIntegerv(pname, JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)ret, NULL));
JSObjectSetTypedArrayData(ctx, (JSObjectRef)ret, data);
[data release];
break;

// boolean[] (with 4 values)
Expand Down Expand Up @@ -1147,15 +1160,21 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
// Float32Array
if( type == GL_FLOAT ) {
array = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeFloat32Array, size);
void *buffer = JSObjectGetTypedArrayDataPtr(ctx, array, NULL);
glGetUniformfv(program, uniform, buffer);
NSMutableData *data = [[NSMutableData alloc] initWithLength:size * sizeof(GLfloat)];
glGetUniformfv(program, uniform, data.mutableBytes);

JSObjectSetTypedArrayData(ctx, array, data);
[data release];
}

// Int32Array
else if( type == GL_INT ) {
array = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeInt32Array, size);
void *buffer = JSObjectGetTypedArrayDataPtr(ctx, array, NULL);
glGetUniformiv(program, uniform, buffer);
NSMutableData *data = [[NSMutableData alloc] initWithLength:size * sizeof(GLint)];
glGetUniformiv(program, uniform, data.mutableBytes);

JSObjectSetTypedArrayData(ctx, array, data);
[data release];
}

// boolean[]
Expand Down Expand Up @@ -1202,8 +1221,10 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
}
else if( pname == GL_CURRENT_VERTEX_ATTRIB ) {
JSObjectRef array = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeFloat32Array, 4);
GLint *values = JSObjectGetTypedArrayDataPtr(ctx, array, NULL);
glGetVertexAttribiv(index, pname, values);
NSMutableData *data = [[NSMutableData alloc] initWithLength:4 * sizeof(GLfloat)];
glGetVertexAttribiv(index, pname, data.mutableBytes);
JSObjectSetTypedArrayData(ctx, array, data);
[data release];
return array;
}
else {
Expand Down Expand Up @@ -1302,13 +1323,11 @@ - (void)deleteVertexArray:(GLuint)vertexArray {

scriptView.currentRenderingContext = renderingContext;

size_t size;
void *pixels = JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)argv[6], &size);

GLuint bytesPerPixel = EJGetBytesPerPixel(type, format);
if( bytesPerPixel && size >= width * height * bytesPerPixel ) {
glReadPixels(x, y, width, height, format, type, pixels);
}
NSMutableData *data = [[NSMutableData alloc] initWithLength:width*height*bytesPerPixel];
glReadPixels(x, y, width, height, format, type, data.mutableBytes);
JSObjectSetTypedArrayData(ctx, (JSObjectRef)argv[6], data);
[data release];

return NULL;
}
Expand Down Expand Up @@ -1463,10 +1482,10 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
if( border == 0 && EJ_ARRAY_MATCHES_TYPE(arrayType, type) ) {
int bytesPerPixel = EJGetBytesPerPixel(type, format);

size_t byteLength;
void *pixels = JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)argv[8], &byteLength);
NSMutableData *data = JSObjectGetTypedArrayData(ctx, (JSObjectRef)argv[8]);
void *pixels = data.mutableBytes;

if( bytesPerPixel && byteLength >= width * height * bytesPerPixel ) {
if( bytesPerPixel && data.length >= width * height * bytesPerPixel ) {
if( unpackFlipY ) {
[EJTexture flipPixelsY:pixels bytesPerRow:(width * bytesPerPixel) rows:height];
}
Expand Down Expand Up @@ -1595,10 +1614,10 @@ - (void)deleteVertexArray:(GLuint)vertexArray {
if( EJ_ARRAY_MATCHES_TYPE(arrayType, type) ) {
int bytesPerPixel = EJGetBytesPerPixel(type, format);

size_t byteLength;
void *pixels = JSObjectGetTypedArrayDataPtr(ctx, (JSObjectRef)argv[8], &byteLength);
NSMutableData *data = JSObjectGetTypedArrayData(ctx, (JSObjectRef)argv[8]);
void *pixels = data.mutableBytes;

if( bytesPerPixel && byteLength >= width * height * bytesPerPixel ) {
if( bytesPerPixel && data.length >= width * height * bytesPerPixel ) {
if( unpackFlipY ) {
[EJTexture flipPixelsY:pixels bytesPerRow:(width * bytesPerPixel) rows:height];
}
Expand Down
7 changes: 4 additions & 3 deletions Source/Ejecta/EJCanvas/WebGL/EJConvertWebGL.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import "EJConvertWebGL.h"
#import "EJConvert.h"
#import <JavaScriptCore/JSTypedArray.h>
#import "EJConvertTypedArray.h"

// FIXME: use C++ with a template?
#define CREATE_JS_VALUE_TO_ARRAY_FUNC(NAME, TYPE, ARRAY_TYPE) \
Expand All @@ -10,8 +10,9 @@
} \
JSObjectRef jsObject = (JSObjectRef)value; \
if( JSObjectGetTypedArrayType(ctx, jsObject) == ARRAY_TYPE ) { \
size_t byteLength; \
TYPE *arrayValue = JSObjectGetTypedArrayDataPtr(ctx, jsObject, &byteLength); \
NSMutableData *data = JSObjectGetTypedArrayData(ctx, jsObject); \
size_t byteLength = data.length; \
TYPE *arrayValue = data.mutableBytes; \
GLsizei count = (GLsizei)(byteLength/sizeof(TYPE)); \
if( arrayValue && count && (count % elementSize) == 0 ) { \
*numElements = count / elementSize; \
Expand Down
5 changes: 3 additions & 2 deletions Source/Ejecta/EJConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ static inline void *JSValueGetPrivate(JSValueRef v) {
// undefined) will crash the app. So we check for these first.

#if __LP64__
#define JSValueTagMask (0xffff000000000000ll | 0x2ll)
return !((int64_t)v & JSValueTagMask) ? JSObjectGetPrivate((JSObjectRef)v) : NULL;
return !((int64_t)v & 0xffff000000000002ll)
? JSObjectGetPrivate((JSObjectRef)v)
: NULL;
#else
return JSObjectGetPrivate((JSObjectRef)v);
#endif
Expand Down
26 changes: 8 additions & 18 deletions Source/Ejecta/EJConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ JSValueRef NSStringToJSValue( JSContextRef ctx, NSString *string ) {
// This functions comes in a 64bit and 32bit flavor, since the NaN-Boxing
// in JSC works a bit differently on each platforms. For an explanation
// of the taggging refer to JSC/runtime/JSCJSValue.h
// The 32bit version just calls the normal JSValueToNumber() function
// and is thus a lot slower.

#if __LP64__ // arm64 version
double JSValueToNumberFast(JSContextRef ctx, JSValueRef v) {
double JSValueToNumberFast(JSContextRef ctx, JSValueRef v) {
#if __LP64__ // arm64 version
union {
int64_t asInt64;
double asDouble;
Expand All @@ -50,22 +52,10 @@ JSValueRef NSStringToJSValue( JSContextRef ctx, NSString *string ) {
else {
return 0; // false, undefined, null, object
}
}
#else // armv7 version
double JSValueToNumberFast( JSContextRef ctx, JSValueRef v ) {
struct {
unsigned char cppClassData[4];
union {
double asDouble;
struct { int32_t asInt; int32_t tag; } asBits;
} payload;
} *decoded = (void *)v;

return decoded->payload.asBits.tag < 0xfffffff9
? decoded->payload.asDouble
: decoded->payload.asBits.asInt;
}
#endif
#else // armv7 version
return JSValueToNumber(ctx, v, NULL);
#endif
}

void JSValueUnprotectSafe( JSContextRef ctx, JSValueRef v ) {
if( ctx && v ) {
Expand Down
75 changes: 75 additions & 0 deletions Source/Ejecta/EJConvertTypedArray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

/*!
@enum JSType
@abstract A constant identifying the Typed Array type of a JSValue.
@constant kJSTypedArrayTypeNone Not a Typed Array.
@constant kJSTypedArrayTypeInt8Array Int8Array
@constant kJSTypedArrayTypeInt16Array Int16Array
@constant kJSTypedArrayTypeInt32Array Int32Array
@constant kJSTypedArrayTypeUint8Array Int8Array
@constant kJSTypedArrayTypeUint8ClampedArray Int8ClampedArray
@constant kJSTypedArrayTypeUint16Array Uint16Array
@constant kJSTypedArrayTypeUint32Array Uint32Array
@constant kJSTypedArrayTypeFloat32Array Float32Array
@constant kJSTypedArrayTypeFloat64Array Float64Array
@constant kJSTypedArrayTypeArrayBuffer ArrayBuffer
*/
typedef enum {
kJSTypedArrayTypeNone = 0,
kJSTypedArrayTypeInt8Array = 1,
kJSTypedArrayTypeInt16Array = 2,
kJSTypedArrayTypeInt32Array = 3,
kJSTypedArrayTypeUint8Array = 4,
kJSTypedArrayTypeUint8ClampedArray = 5,
kJSTypedArrayTypeUint16Array = 6,
kJSTypedArrayTypeUint32Array = 7,
kJSTypedArrayTypeFloat32Array = 8,
kJSTypedArrayTypeFloat64Array = 9,
kJSTypedArrayTypeArrayBuffer = 10
} JSTypedArrayType;

/*!
@function
@abstract Setup the JSContext for use of the Typed Array functions.
@param ctx The execution context to use
*/
void JSContextPrepareTypedArrayAPI(JSContextRef ctx);

/*!
@function
@abstract Returns a JavaScript value's Typed Array type
@param ctx The execution context to use.
@param value The JSObject whose Typed Array type you want to obtain.
@result A value of type JSTypedArrayType that identifies value's Typed Array type
*/
JSTypedArrayType JSObjectGetTypedArrayType(JSContextRef ctx, JSObjectRef object);

/*!
@function
@abstract Creates an empty JavaScript Typed Array with the given number of elements
@param ctx The execution context to use.
@param arrayType A value of type JSTypedArrayType identifying the type of array you want to create
@param numElements The number of elements for the array.
@result A JSObjectRef that is a Typed Array or NULL if there was an error
*/
JSObjectRef JSObjectMakeTypedArray(JSContextRef ctx, JSTypedArrayType arrayType, size_t numElements);

/*!
@function
@abstract Returns a copy of the Typed Array's data
@param ctx The execution context to use.
@param value The JSObject whose Typed Array data you want to obtain.
@result A copy of the Typed Array's data or NULL if the JSObject is not a Typed Array
*/
NSMutableData *JSObjectGetTypedArrayData(JSContextRef ctx, JSObjectRef object);

/*!
@function
@abstract Replaces a Typed Array's data
@param ctx The execution context to use.
@param value The JSObject whose Typed Array data you want to replace
*/
void JSObjectSetTypedArrayData(JSContextRef ctx, JSObjectRef object, NSData *data);

Loading

0 comments on commit 08741b4

Please sign in to comment.