Chapter 13. WebCL
In this chapter, we describe our experiments using OpenCL in client-side web applications. We have developed a Firefox 4.0 extension that provides OpenCL bindings to JavaScript, allowing web applications to leverage the powerful parallel computing capabilities of modern CPUs and GPUs. The results are promising: We have achieved orders of magnitude speed-up over JavaScript under favorable (highly parallel) workloads, such as image processing. Although many concerns and challenges remain related to security, robustness, the scarce availability of OpenCL drivers, and so on, we believe that this “WebCL” approach represents a way forward for high-performance, interactive web applications.
Keywords Browser extension, HTML5, image processing, JavaScript, OpenCL, WebCL, WebGL

Introduction

The World Wide Web has several advantages over traditional software platforms. An application or service that is hosted on the web can be accessed from any device at any time, does not have to be installed, is always up-to-date, and facilitates flexible sharing of data and computation between the client and the server. As a result, an increasing number of applications are being developed for, and migrated to, the web.
At the same time, web applications are also becoming more complex, more interactive, and more visual. This creates demand for high-performance computing in a browser environment. Browser vendors have been able to meet this demand by improving the performance of JavaScript execution by orders of magnitude during the past few years. However, JavaScript engines are still unable to match the performance of native code, and they will continue to lag in performance due to the inherent properties of the language. In particular, JavaScript is not designed to exploit the multithreaded data-parallel computing capabilities available on modern GPUs, CPUs, and digital signal processors.
OpenCL, by contrast, is explicitly designed for that purpose. WebCL is a set of JavaScript bindings for OpenCL that aim to provide access to such capabilities from within a JavaScript application. This allows web developers to tackle computationally intensive tasks such as image processing and physics simulation from an environment that they are used to. These classes of applications are prohibitively slow when developed and run in JavaScript alone. To some degree, WebCL (Khronos Group, n.d.) already meets many of these needs, but this is only partially true: WebGL shaders are severely constrained compared to OpenCL kernels, are heavily biased toward three-dimensional graphics, and can only be executed on the GPU.
This chapter discusses preliminary work on using OpenCL in client-side web applications. The version of WebCL described here has been implemented as a Firefox 4.0 extension, mimicking WebGL in the API design. The WebCL version discussed is available at http://webcl.nokiaresearch.com. The system works on multiple platforms and can provide orders of magnitude speed-up over JavaScript when running compute-intensive workloads such as image processing. However, significant challenges remain in the areas of security, robustness, portability, interoperability with WebGL, interfacing to JavaScript engines, and the availability of OpenCL support in the underlying platforms.

Designing the Framework

The ultimate goal of WebCL is to effectively use all of the underlying computing resources for client-side web applications. Therefore, to reserve and initialize the resources as well as control the execution, we also need a general-purpose framework, a language to describe the processing, and a library to manage the processing.
The approach should be capable of supporting a wide range of device capabilities. For example, mobile and embedded devices typically include a large degree of heterogeneity, with powerful execution environments supported by limited support for programming frameworks. Consequently, the programming language used to describe the computation should be widely and commonly supported by as many platforms as possible, as well as being well known and widely used from a developer's viewpoint.
C is a well-known, widely used, and widely supported language. However, C does not inherently possess any features that support parallel programming and would need enhancements to provide such support. When beginning to consider creating a parallel processing management library, the design suddenly starts to resemble the OpenCL framework. To develop a WebCL framework, this resemblance motivates us to leverage the established approach used for WebGL: a thin layer on top of native drivers, where the native API is bound to JavaScript by following the original API as closely as possible (Table 13.1). The thin layer approach presents us with several benefits: (1) That the adaptation layer has been developed to be as “lightweight” as possible provides for ease of maintenance; (2) porting OpenCL code to WebCL requires minimal effort; and (3) additional abstraction on the native side might restrict the usage, whereas abstracted user-friendly JavaScript utility libraries can be designed on top of this implementation.
Table 13.1 WebCL Abstractions in Relation to OpenCL
OpenCL Abstract Data TypeWebCL Class
cl_command_queueWebCLCommandQueue
cl_contextWebCLContext
WebCLDataObject
cl_device_idWebCLDevice
cl_eventWebCLEvent
cl_kernelWebCLKernel
cl_memWebCLMemoryObject
cl_platform_idWebCLPlatform
cl_programWebCLProgram
cl_samplerWebCLSampler
WebCLUtils
Web usage would require that the approach be specified using generic semantics for portability. The programmer should not need to make choices based on the target platform. The framework should not assume binary compatibility nor depend on a fixed set of system libraries. The framework must therefore exclude OpenCL native and binary kernels. Moreover, host pointers are not supported because there is no intuitive mechanism to implement them in JavaScript and because host pointers could be exploited to expose a possible security hole.
In OpenCL, error management is based on checking return codes that come in the form of either return values or output parameters. The status of each operation must be checked individually. JavaScript utilizes exceptions for error handling, enabling the developer to encase a block of code in a try-catch construct.

WebCL Pilot Implementation

The WebCL API is provided as a Mozilla Firefox browser extension. The framework discussed previously serves as the starting point for this implementation. WebCL is designed to work together with other state-of-the-art web technologies, some of which, such as WebGL, have been recently introduced or are still under development. The Firefox extension gives us a good experimental starting point.

Firefox Extension

A number of implementation approaches were evaluated for WebCL, including direct modification of Mozilla sources, an NPAPI plug-in, and a browser extension. The approach chosen for WebCL is a Firefox extension, implemented as a native code XPCOM (Cross Platform Component Object Model) component, enabling it to be developed independently from the Mozilla code base. This approach enables the implementation to be demonstrated and promoted without requiring the user to install and run a custom-built browser. Although NPAPI would have provided an attractive path for implementing WebCL (targeting a wide range of existing browsers), it is not well suited to providing APIs such as WebCL for use in a JavaScript context.
Mozilla Firefox is built on top of the Mozilla application framework. The user interface of the application is written in XUL, Mozilla's XML user interface markup language. The internals largely utilize XPCOM. XPCOM components are relatively separate functional units that utilize and provide services for other components. XPCOM provides a set of core functionalities, components, and classes, such as component management, file abstraction, object message passing, and memory management. Components can be written in a variety of languages. To facilitate access to the native code OpenCL library, C++ was chosen (despite the fact that JavaScript is more common and actually encouraged for extensions by Mozilla).
WebCL is implemented in two layers as shown in Figure 13.1: WebCL and the CLWrapper. The layer providing the actual WebCL API is called simply WebCL, and it contains all the browser-specific functionality, the XPCOM component, and JavaScript interoperability. WebCL builds on top of the CLWrapper layer, which is essentially a pure C++ wrapper for the OpenCL C API enabling WebCL to be ported for other browsers written in C++ by simply implementing a new WebCL layer. In addition, CLWrapper provides object life cycle management and a type-based mechanism for retrieving information parameters as well as setting kernel arguments.
B9780123877666000360/f13-01-9780123877666.jpg is missing
Figure 13.1
Basic block diagram of WebCL implementation.

Connecting JavaScript and OpenCL

One of the most significant differences between C/C++ and JavaScript environments is JavaScript's support for resource management. In C and C++, dynamically allocated memory must be explicitly released by the programmer when the memory block is no longer needed. In JavaScript, the memory used by a variable is released by an automatic memory management system, known as garbage collection, once the system detects that the memory is no longer in use. However, the garbage collection overhead can be high and is not usually activated immediately, counting on the runtime system to determine when to release this memory. This can lead to unexpected and undesirable behavior from an application's perspective because performance can be heavily affected by a garbage collection. Moreover, if the garbage collection is extended to the task of resource management in general, any limited resource may not be released in a timely manner.
Some OpenCL resources can be very limited, such as the number of simultaneous OpenCL contexts. OpenCL memory buffers can allocate a large chunk of device memory, again setting an upper limit for the number of objects allocated simultaneously. In an environment using garbage collection, it may not be possible to ensure that these resources are deterministically released, leading to application errors that are difficult to reproduce and correct.
As the WebCL API makes an attempt to follow the characteristics of a standard JavaScript API, the implementation relies heavily on garbage collection. Therefore, applications should avoid re-creating many WebCL objects but instead reuse them for as long as possible. It is likely that an option to explicitly release OpenCL-related resources will be added in the future, even though it would make the WebCL API slightly more complex and less subject to problems.
In many situations, the OpenCL API uses a void pointer for arbitrary data, and the data type is tied to the context. This is an unfortunate result of C's limited type system. For example, clGetDeviceInfo takes an argument called param_name, an enumeration denoting the actual piece of requested device information, and a void pointer argument called param_value, denoting the memory buffer to which the resulting value should be written. The enumeration assigns context and interpretation to the data written through the void pointer.
Given that JavaScript does not have pointers, and the type system is dynamic instead of static, a similar approach is obviously not achievable. Thus, type information needs to be added to certain calls, such as clGetDeviceInfo or clSetKernelArg. Sometimes, the type can be omitted because it can be deduced from the given value, when the value is an object of specific type. This is possible, for example, in the case of memory objects.
Many WebCL use cases (e.g., imaging and signal processing) require large amounts of data to be transferred between a JavaScript context and OpenCL buffers. In general terms, the type and nature of the data are only known by the OpenCL kernel code and cannot be determined by the WebCL API. Whereas the OpenCL C API uses a raw void pointer type and requires that the library handle copying the data to device memory, employing this approach with JavaScript does not work because the language has no void pointers. JavaScript arrays are an obvious choice, but they are inherently inefficient due to the need to copy the data item by item. Perhaps even more important, an array representation of the data imposes a certain structured interpretation on the data that may not be valid in all cases and is (in some cases) more limiting than using an arbitrary binary blob referenced through a void pointer.
An ideal solution would be to use an object that is able to hold a buffer of arbitrary sized data while providing multiple different views or interpretations for assigning meaning to the data. Such an object exists in the form of the data types TypedArray and ArrayBuffer, which are already supported in the WebGL API. However, these are not universally supported at this time. Although there is an implementation in the upcoming release of Mozilla Firefox, the support is only partial. For example, XPCOM interface definitions are missing, which makes using TypedArrays from browser extensions complicated.
Although in the future WebCL should use TypedArray directly, in this implementation we introduced an extra level of abstraction to keep the API and the implementation clean and to allow users to create implementations for browsers not supporting TypedArray. This abstraction is called WebCLDataObject. This becomes the data object used in the WebCL API, whereas OpenCL would use a void pointer in relation to memory objects. In addition, the data object provides certain utilities to support processing image data within a canvas element, which is important to support the examples discussed in the following sections.

WebCL Hands-on

The usage of WebCL resembles OpenCL coding. The difference from native coding is the host code that is now JavaScript code in web applications. Naturally, the different environments, JavaScript versus C, introduce several differences from the application standpoint, but these differences are not discussed in detail in this chapter. Also, we choose to omit checks that are normally essential when developing quality applications. The emphasis in these examples is on highlighting the differences between OpenCL and WebCL.
In the following, WebCL syntax is demonstrated by comparing it with an OpenCL implementation of gray scale conversion of a color image. The complete and additional WebCL examples can be found at http://webcl.nokiaresearch.com. The kernel assumes the input image is provided in a pixel format of RGBA color components, with a byte for each color component, and returns the gray scale image in the same format. As mentioned previously, the kernel source code is written in OpenCL C, and it can be identical for both OpenCL and WebCL.
Kernel source:
__kernel void desaturate(
__global uchar4* src,
__global uchar4* dst,
uint width,
uint height) {
int x = get_global_id(0);
int y = get_global_id(1);
int i = y * width + x;
uchar4 color = src[i];
uchar lum =
(uchar)(0.30f * color.x + 0.59f * color.y + 0.11f * color.z);
dst[i] = (uchar4)(lum, lum, lum, 255);
}
The host source codes for both OpenCL and WebCL implementations are presented on the following pages. The implementations are compared in parallel as the reader is introduced to the application in logical steps. First, an OpenCL context must be set up. The default device of the first available platform is chosen without discrimination in order to keep the example simple.
The following source code illustrates how information retrieval from the OpenCL API is simplified in WebCL. In native OpenCL, it is necessary to first obtain the number of platforms, allocate necessary memory buffers, and, finally, retrieve the platform information and store it in the allocated buffer. WebCL implements this as a simple return value. A property array is delivered as a plain JavaScript array, and constants, as well as certain high-level functionality such as retrieving platforms and creating contexts, are accessed via a global WebCL object. A notable difference is the omission of error reporting/handling code. Error handling on WebCL is implemented using the exception mechanism of JavaScript (callbacks may be implemented as well in the future).
OpenCL:
cl_uint numPlatforms;
cl_int status = clGetPlatformIDs(0, NULL, &numPlatforms);
cl_platform_id* platforms = (cl_platform_id *)malloc(sizeof(cl_platform_id)*numPlatforms);
status = clGetPlatformIDs(numPlatforms, platforms, NULL);
cl_context_properties clProperties[3] = {
CL_CONTEXT_PLATFORM,
(cl_context_properties)platforms[0],
0 };
cl_context ctx = clCreateContextFromType(
clProperties,
CL_DEVICE_TYPE_DEFAULT,
NULL,
NULL,
&status);
WebCL:
var platforms = WebCL.getPlatformIDs();
var ctx = WebCL.createContextFromType(
[WebCL.CL_CONTEXT_PLATFORM, platforms[0]],
WebCL.CL_DEVICE_TYPE_DEFAULT);
The next step is to set up buffers. The size of both the input and the output buffers, bufSize, is the size of the image pixel data in bytes (i.e., the number of pixels on the image multiplied by the number of bytes per pixel). Because WebCL utilizes an object-oriented programming paradigm, createBuffer is called as a member function of the ctx object, instead of ctx being given as a parameter. Host pointers are omitted in WebCL.
OpenCL:
cl_mem bufIn = clCreateBuffer(
ctx,
CL_MEM_READ_ONLY,
bufSize,
NULL,
&status);
cl_mem bufOut = clCreateBuffer(
ctx,
CL_MEM_WRITE_ONLY,
bufSize,
NULL,
&status);
WebCL:
var bufIn = ctx.createBuffer(WebCL.CL_MEM_READ_ONLY, bufSize);
var bufOut = ctx.createBuffer(WebCL.CL_MEM_WRITE_ONLY, bufSize);
Next, the OpenCL program needs to be created from kernel sources and then built. Native OpenCL takes the source code as an array of pointers to C strings. This array offers the opportunity to divide the string into multiple parts. In WebCL, the need for dividing the source string is not recognized and so the count parameter can be removed as well.
OpenCL:
cl_program program = clCreateProgramWithSource(
ctx,
1,
(const char **)&kernelSrc,
&kernelLength,
&status);
size_t devicesLen;
status = clGetContextInfo(
ctx,
CL_CONTEXT_DEVICES,
0,
NULL,
&devicesLen);
cl_device_id* devices = (cl_device_id *)malloc(devicesLen);
status = clGetContextInfo(
ctx,
CL_CONTEXT_DEVICES,
devicesLen,
devices,
NULL);
status = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
WebCL:
var program = ctx.createProgramWithSource(kernelSrc);
var devices = ctx.getContextInfo(WebCL.CL_CONTEXT_DEVICES);
try {
program.buildProgram (devices, "");
} catch(e) { … }
We create the kernel and set its arguments in the following source lines. The actual types of kernel arguments must be known for proper WebCL type conversions. In JavaScript, variable typing is dynamic and thus very different from C. Because the kernel code expects data of an exact size and type, the JavaScript value must be correctly converted. WebCL objects, such as memory objects bufIn and bufOut in the example, do not require the type because there is no room for interpretation in this case, so bufOut is used without specifying an explicit type as in the example.
OpenCL:
cl_kernel kernel = clCreateKernel(program, "desaturate", &status);
status = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*)&bufIn);
status |= clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*)&bufOut);
status |= clSetKernelArg(kernel, 2, sizeof(cl_uint), (void*)&width);
status |= clSetKernelArg(kernel, 3, sizeof(cl_uint), (void*)&height);
WebCL:
ar kernel = program.createKernel("desaturate");
kernel.setKernelArg(0, bufIn, WebCL.types.MEMORY_OBJECT);
kernel.setKernelArg(1, bufOut);
kernel.setKernelArg(2, width, WebCL.types.UINT);
kernel.setKernelArg(3, height, WebCL.types.UINT);
Finally, we require a command queue, into which we enqueue a write command to copy source data, the execution of the kernel, and a read command to copy the result data. In relation to OpenCL memory objects, the data pointer (pixels) is replaced by the use of a WebCLDataObject in WebCL that wraps the binary data without using a JavaScript array, as discussed previously.
OpenCL:
cl_command_queue cmdQueue = clCreateCommandQueue(
ctx,
devices[0],
0,
&status);
status = clEnqueueWriteBuffer(
cmdQueue,
bufIn,
CL_FALSE,
0,
bufSize,
pixels,
0,
NULL,
NULL);
status = clEnqueueNDRangeKernel(
cmdQueue,
kernel,
2,
NULL,
globalWS,
localWS,
0,
NULL,
NULL);
status = clEnqueueReadBuffer(
cmdQueue,
bufOut,
CL_FALSE,
0,
bufSize,
pixels,
0,
NULL,
NULL);
status = clFinish(cmdQueue);
WebCL:
var cmdQueue = ctx.createCommandQueue (devices[0], 0);
cmdQueue.enqueueWriteBuffer (bufIn, false, 0, bufSize, dataObject, []);
cmdQueue.enqueueNDRangeKernel(kernel, globalWS.length, [], globalWS, localWS, []);
cmdQueue.enqueueReadBuffer (bufOut, false, 0, bufSize, dataObject, []);
cmdQueue.finish ();
In WebCL, the resulting data is now available on the data object and can be utilized further by the application.

Web Photo Editor

To test and benchmark WebCL in a realistic application scenario, we provide a web-based photo editor prototype, which is shown in Figure 13.2. The editor is available for testing at http://webcl.nokiaresearch.com.
B9780123877666000360/f13-02-9780123877666.jpg is missing
Figure 13.2
Prototype photo editor based on WebCL, WebGL, and HTML5.
The Web Photo Editor example features a touch screen optimized user interface with buttons and sliders and also an experimental set of image processing operators. The operators include basic adjustments such as cropping, scaling, and rotation, as well as red-eye removal, blurring, smoothing, edge enhancement, distortion, and other special effects. The application runs entirely in the browser, without any server-side processing.
Due to WebGL and WebCL, most of the image processing operators are able to process dozens or even hundreds of megapixels per second on a regular laptop computer. This makes for a smooth, interactive editing experience that would remain a distant dream without being able to harness the parallel processing power of modern GPUs and multi-core, vector-accelerated CPUs.
At the heart of the editing experience lies the painting mode. This mode allows image processing operators to be applied interactively on selected parts of the image using a paintbrush (Figure 13.3). To identify the main subject in a photo, you could first apply a global blur filter and then “paint in” the desired parts from the original image.
B9780123877666000360/f13-03-9780123877666.jpg is missing
Figure 13.3
Interactive painting in the photo editor. The user can apply an image processing operator, such as the Sobel edge detector in this example, on selected parts of the image by painting with a circular brush. The “Threshold” button on the left brings up a slider for adjusting the sensitivity of the edge detector.
When the user selects an image for editing, four WebGL textures are allocated: (1) a source texture for storing the original image, (2) a temporary texture for storing intermediate results, (3) an alpha mask texture for the interactive paint mode, and (4) a destination texture for storing the processed and painted final image. The roles of the source and destination textures are reversed each time the user decides to “Apply” the pending edits. All the textures are the same size as the original image.
Most of the processing time in the photo editor is spent in WebGL shaders, WebCL kernels, and moving data back and forth. A WebGL shader is invoked every time the canvas needs to be redrawn, whether due to zooming, panning, painting, resizing the window, or other activities. Because all shaders and kernels operate on full-size images, the computational load, as well as the amount of data transferred back and forth between the CPU and the GPU, can be very high. Most of the data traffic is in fact redundant, stemming from the current restriction on sharing of textures between WebGL and WebCL.

Discussion

The WebCL implementation utilizes and works together with several novel technologies that are still under development. This makes the setup relatively unstable for commercial adoption at this time. However, to evaluate the performance opportunities provided by WebCL, we provide early performance data in Table 13.2. In this setup, a 2.5 MPix RGB image was filtered using the previously described grayscaling filter and bilateral filter. The bilateral filter is implemented according to the straight bilateral definition. The numbers depict the achieved improvement in performance compared to a JavaScript implementation. The results depend heavily on execution environment, and we intend these results to be an indication of performance rather than to be fully defined.
Table 13.2 Measurement Data of Preliminary Experiments, Speedup Compared to JavaScript
FilterJavaScriptWebCL@CPUWebCL@GPU
Grayscaling1.01.52.5
Bilateral filtering1.020200
When WebCL applications are written, or existing code utilizing OpenCL is ported to WebCL, it is essential to realize that specific characteristics of the target environment can have a major impact on the quality and the usefulness of the result. Because WebCL is built on top of the OpenCL API, there will be unavoidable overhead (especially when data is being moved between the JavaScript context and native code). For this reason, it is generally best to avoid frequent data transfers. Once the data is pushed to the native side, it is best to keep it there until it is required in the JavaScript application.
During application development, no assumptions should be made about the underlying platform. Modern web applications should be expected to be run on a wide variety of devices ranging from desktops and mobile devices to game consoles. When executed in a native environment, it is more likely that an application will be specialized to take advantage of a specific type of platform, such as desktop workstations or AMD GPUs. Web applications should take care to gather a sufficient amount of information about the execution environment before performing any specialization. For example, kernel compilation times can be expected to be much higher when running on a mobile device. Also, as with native OpenCL applications, the kernel execution parameters should be set so that maximum values are retrieved as information parameters are honored.
As discussed previously, memory management in WebCL is based on the garbage collector built into the JavaScript runtime. Moreover, because limited OpenCL hardware and runtime resources are also associated with this mechanism through the WebCL API, overuse can lead to problems. Therefore, careful thought must be given to resource use throughout the application. Resource-heavy objects, such as contexts or memory objects, should be created once and reused as much as possible.
Algorithms used in kernels should be designed to be fast and efficient. However, any optimizations should be meaningful and evaluated against the fact that the web context is easily dominated by JavaScript speed (or lack thereof) and garbage collection overhead. Also, the web favors cross-platform solutions, so any designs sacrificing this should be avoided.
In the future, we hope to see more widespread support for the TypedArray data type in HTML and JavaScript. For example, the canvas element would benefit from the ability to read and write the complete image pixel data with a TypedArray type versus using the currently supported pixel-by-pixel approach. In addition, WebCL inputs and outputs should ideally be bound to WebGL textures and buffers without any intermediate buffers. Unfortunately, because the current prototype is a Firefox add-on (as opposed to an integral part of the browser), our WebCL extension currently has no means to access WebGL resources. This can be remedied by either integrating WebCL into the browser engine or persuading the browser vendors to expose the necessary C/C++ APIs to extensions.
In the web environment, any API that the browser exposes to the outside world will, without exception, be attacked by malware. WebCL must therefore be designed with the assumption that every piece of host code or kernel code is potentially hostile. If the underlying OpenCL driver or platform is not sufficiently secure and robust, then the WebCL implementation must either put the driver/platform combination on a blacklist or provide additional safeguards via process isolation, memory protection, kernel code analysis, or other means. Unfortunately, the current-generation OpenCL hardware does not support context switches and, hence, driver controls for misbehaving code tend to be harsh and intrusive.
Writing performance portable OpenCL C can be very challenging. Moreover, most laptop and desktop computers, in addition to smartphones or other portable devices, do not yet ship with OpenCL drivers preinstalled. To make WebCL prevalent, it may be necessary to ship a CPU implementation of OpenCL with the browser.

Summary

In this chapter, we described an early implementation of WebCL as a Firefox extension, which provides a JavaScript API corresponding to the OpenCL API. The extension works on top of native OpenCL drivers, and OpenCL C kernel descriptions are passed as such to the OpenCL driver.
This approach to developing WebCL is promising, although there remain open issues, with security being the most obvious. Nevertheless, WebCL takes web applications to the next level by introducing remarkable performance. GPUs are already accessible through WebGL, but WebCL would further the use of GPUs for web-based execution. For the computation kernels, the CPU gave up to 20× and the GPU up to 200× performance compared to plain JavaScript on a modern laptop. These results show that the approach provides good performance in use cases that would otherwise be completely outside of JavaScript scope.
Reference
Khronos Group, WebGL—OpenGL ES 2.0 for the Web, https://www.khronos.org/webgl; Retrieved March 2011.
Further Reading and Relevant Websites
Khronos Group, OpenCL Overview: OpenCL—The Open Standard for Parallel Programming of Heterogeneous Systems, https://www.khronos.org/opencl; Retrieved March 2011.
Khronos Group, Khronos Typed Array API Registry, http://www.khronos.org/registry/typedarray.
Khronos OpenCL Working Group, The OpenCL Specification. (Version 1.1, Document Revision 36) (2010) The Khronos Group, Beaverton, OR.
Mozilla Mozilla, Firefox, http://www.mozilla.com/en-US/firefox.
Mozilla Mozilla, Firefox Nightlies, http://www.mozilla.org/projects/minefield.
Mozilla Developer Network, Plugins, https://developer.mozilla.org/en/Plugins.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset