function B3DAcceleratorPlugin() { "use strict"; var DEBUG = 0; // 0 = off, 1 = some, 2 = lots var DEBUG_WAIT = false; // wait after each frame var renderers = []; // list of all renderers var rendererId = 0; // unique id for each renderer var currentRenderer = null; // set by makeCurrent() var OpenGL = null; // set by setOpenGL() var GL = null; // set by setOpenGL() /* Renderer creation flags: B3D_SOFTWARE_RENDERER: Enable use of software renderers B3D_HARDWARE_RENDERER: Enable use of hardware renderers B3D_STENCIL_BUFFER: Request stencil buffer B3D_ANTIALIASING: Request antialiasing in the renderer. B3D_STEREO: Request stereo visual from the renderer B3D_SYNCVBL: Request VBL sync More flags may be added - if they are not supported by the platform code the creation primitive should fail. */ var B3D_SOFTWARE_RENDERER = 0x0001; var B3D_HARDWARE_RENDERER = 0x0002; var B3D_STENCIL_BUFFER = 0x0004; var B3D_ANTIALIASING = 0x0008; // ignored var B3D_STEREO = 0x0010; // ignored var B3D_SYNCVBL = 0x0020; // ignored return { getModuleName: function() { return 'B3DAcceleratorPlugin (SqueakJS)'; }, interpreterProxy: null, primHandler: null, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; this.vm = this.interpreterProxy.vm; this.primHandler = this.vm.primHandler; this.prevDCCallback = this.primHandler.display.changedCallback; this.primHandler.display.changedCallback = () => { if (this.prevDCCallback) this.prevDCCallback(); for (const renderer of renderers) { this.adjustCanvas(renderer); } }; return true; }, setOpenGL: function(OpenGLPlugin) { OpenGL = OpenGLPlugin; GL = OpenGLPlugin.GL; if (currentRenderer) OpenGL.makeCurrent(currentRenderer); }, makeCurrent(renderer) { if (currentRenderer !== renderer) { currentRenderer = renderer; if (OpenGL) OpenGL.makeCurrent(renderer); } if (renderer.webgl.isContextLost()) { if (!renderer.warning) { console.warn("B3DAccel: WebGL context lost"); var div = document.createElement("div"); div.style.position = "absolute"; div.style.left = renderer.canvas.offsetLeft + "px"; div.style.top = renderer.canvas.offsetTop + "px"; div.style.width = renderer.canvas.width + "px"; div.style.height = renderer.canvas.height + "px"; div.style.backgroundColor = "rgba(0,0,0,0.8)"; div.style.color = "white"; div.style.fontFamily = "sans-serif"; div.style.fontSize = "24px"; div.style.textAlign = "center"; div.style.lineHeight = renderer.canvas.height + "px"; div.style.pointerEvents = "none"; div.style.cursor = "normal"; div.innerHTML = "WebGL context lost"; document.body.appendChild(div); renderer.warning = div; } } }, currentFromStack: function(i) { var renderer = this.interpreterProxy.stackObjectValue(i); if (!renderer.webgl) return null; this.makeCurrent(renderer); return renderer; }, primitiveAllocateTexture: function(argCount) { if (argCount !== 4) return false; var h = this.interpreterProxy.stackIntegerValue(0); var w = this.interpreterProxy.stackIntegerValue(1); var d = this.interpreterProxy.stackIntegerValue(2); if (!this.currentFromStack(3)) return false; if (w & (w-1)) return false; /* not power of two */ if (h & (h-1)) return false; /* not power of two */ DEBUG > 0 && console.log("B3DAccel: primitiveAllocateTexture", w, h, d); var tex = [0]; OpenGL.glGenTextures(1, tex); var texture = tex[0]; OpenGL.glBindTexture(GL.TEXTURE_2D, texture); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.REPEAT); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.REPEAT); OpenGL.glTexEnvi(GL.TEXTURE_ENV, GL.TEXTURE_ENV_MODE, GL.MODULATE); OpenGL.glTexImage2D(GL.TEXTURE_2D, 0, GL.RGBA, w, h, 0, GL.BGRA, GL.UNSIGNED_BYTE, null); return this.primHandler.popNandPushIfOK(argCount + 1, texture); }, primitiveSetVerboseLevel: function(argCount) { if (argCount !== 1) return false; var level = this.interpreterProxy.stackIntegerValue(0); DEBUG > 0 && console.log("B3DAccel: primitiveSetVerboseLevel", level); this.interpreterProxy.pop(argCount); return true; }, primitiveCompositeTexture: function(argCount) { if (argCount !== 7) return false; if (!this.currentFromStack(6)) return false; var texHandle = this.interpreterProxy.stackIntegerValue(5); var x = this.interpreterProxy.stackIntegerValue(4); var y = this.interpreterProxy.stackIntegerValue(3); var w = this.interpreterProxy.stackIntegerValue(2); var h = this.interpreterProxy.stackIntegerValue(1); var translucent = this.interpreterProxy.booleanValueOf(this.interpreterProxy.stackValue(0)); if (this.interpreterProxy.failed()) return false; DEBUG > 1 && console.log("B3DAccel: primitiveCompositeTexture", texHandle, x, y, w, h, translucent); var result = this.b3dxCompositeTexture(texHandle, x, y, w, h, translucent); if (!result) return false; this.interpreterProxy.pop(argCount); return true; }, primitiveCreateRendererFlags: function(argCount) { if (argCount !== 5) return false; var flags = this.interpreterProxy.stackIntegerValue(4); var x = this.interpreterProxy.stackIntegerValue(3); var y = this.interpreterProxy.stackIntegerValue(2); var w = this.interpreterProxy.stackIntegerValue(1); var h = this.interpreterProxy.stackIntegerValue(0); if (flags & ~(B3D_HARDWARE_RENDERER | B3D_SOFTWARE_RENDERER | B3D_STENCIL_BUFFER)) return false; DEBUG > 0 && console.log("B3DAccel: primitiveCreateRendererFlags", x, y, w, h, flags); // create WebGL canvas var canvas = document.createElement("canvas"); canvas.classList.add("b3daccel"); canvas.width = w; canvas.height = h; canvas.style.position = "absolute"; canvas.style.backgroundColor = "transparent"; canvas.style.pointerEvents = "none"; canvas.style.cursor = "normal"; var options = { depth: true, alpha: false, antialias: true }; if (flags & B3D_STENCIL_BUFFER) options.stencil = true; var webgl = canvas.getContext("webgl", options); if (!webgl) return false; // create renderer rendererId++; var renderer = this.primHandler.makeStString("WebGL#" + rendererId); renderers.push(renderer); renderer.rendererId = rendererId; renderer.canvas = canvas; renderer.webgl = webgl; // set viewport this.b3dxSetViewport(renderer, x, y, w, h); document.body.appendChild(canvas); // make renderer accessible to other plugins this.makeCurrent(renderer); DEBUG > 0 && console.log("B3DAccel: created renderer", rendererId); return this.primHandler.popNandPushIfOK(argCount + 1, renderer); }, primitiveDestroyRenderer: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; DEBUG > 0 && console.log("B3DAccel: primitiveDestroyRenderer", currentRenderer.rendererId); renderers = renderers.filter(r => r !== currentRenderer); if (OpenGL) OpenGL.destroyGL(currentRenderer); if (currentRenderer.warning) { currentRenderer.warning.remove(); currentRenderer.warning = null; } currentRenderer.canvas.remove(); currentRenderer.canvas = null; currentRenderer.webgl = null; currentRenderer = null; DEBUG > 0 && console.log("B3DAccel: destroyed renderer", rendererId); this.interpreterProxy.pop(argCount); return true; }, primitiveDestroyTexture: function(argCount) { if (argCount !== 2) return false; var texture = this.interpreterProxy.stackIntegerValue(0); if (!this.currentFromStack(1)) return false; DEBUG > 0 && console.log("B3DAccel: primitiveDestroyTexture", texture); OpenGL.glDeleteTextures(1, [texture]); this.interpreterProxy.pop(argCount); return true; }, primitiveFinishRenderer: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; DEBUG > 1 && console.log("B3DAccel: primitiveFinishRenderer", currentRenderer); OpenGL.glFinish(); this.interpreterProxy.pop(argCount); return true; }, primitiveFlushRenderer: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; DEBUG > 1 && console.log("B3DAccel: primitiveFlushRenderer", currentRenderer); OpenGL.glFlush(); this.interpreterProxy.pop(argCount); return true; }, primitiveGetRendererSurfaceHandle: function(argCount) { // this would allow BitBlt to draw directly into the renderer surface // but it was only ever implemented for Direct3D not OpenGL // so the image will use a texture overlay instead if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; DEBUG > 1 && console.log("B3DAccel: UNIMPLEMENTED primitiveGetRendererSurfaceHandle", currentRenderer); return false; }, primitiveGetIntProperty: function(argCount) { if (argCount !== 2) return false; var property = this.interpreterProxy.stackIntegerValue(0); if (!this.currentFromStack(1)) return false; DEBUG > 1 && console.log("B3DAccel: primitiveGetIntProperty", property); var value = this.b3dxGetIntProperty(currentRenderer, property); return this.primHandler.popNandPushIfOK(argCount + 1, value); }, primitiveGetRendererSurfaceWidth: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; var width = currentRenderer.canvas.width; DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererSurfaceWidth", width); return this.primHandler.popNandPushIfOK(argCount + 1, width); }, primitiveGetRendererSurfaceHeight: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; var height = currentRenderer.canvas.height; DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererSurfaceHeight", height); return this.primHandler.popNandPushIfOK(argCount + 1, height); }, primitiveGetRendererSurfaceDepth: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; var depth = 32; DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererSurfaceDepth", depth); return this.primHandler.popNandPushIfOK(argCount + 1, depth); }, primitiveGetRendererColorMasks: function(argCount) { if (argCount !== 2) return false; var array = this.interpreterProxy.stackObjectValue(0); if (this.currentFromStack(1)) return false; if (array.pointersSize() !== 4) return false; var masks = [0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000]; for (var i = 0; i < 4; i++) { array.pointers[i] = this.interpreterProxy.positive32BitIntegerFor(masks[i]); } DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererColorMasks", masks); this.interpreterProxy.pop(argCount); return true; }, primitiveSetBufferRect: function(argCount) { if (argCount !== 5) return false; if (!this.currentFromStack(4)) return false; var x = this.interpreterProxy.stackIntegerValue(3); var y = this.interpreterProxy.stackIntegerValue(2); var w = this.interpreterProxy.stackIntegerValue(1); var h = this.interpreterProxy.stackIntegerValue(0); DEBUG > 1 && console.log("B3DAccel: primitiveSetBufferRect", x, y, w, h); this.b3dxSetViewport(currentRenderer, x, y, w, h); this.interpreterProxy.pop(argCount); return true; }, primitiveSetViewport: function(argCount) { if (argCount !== 5) return false; if (!this.currentFromStack(4)) return false; var x = this.interpreterProxy.stackIntegerValue(3); var y = this.interpreterProxy.stackIntegerValue(2); var w = this.interpreterProxy.stackIntegerValue(1); var h = this.interpreterProxy.stackIntegerValue(0); DEBUG > 1 && console.log("B3DAccel: primitiveSetViewport", x, y, w, h); this.b3dxSetViewport(currentRenderer, x, y, w, h); this.interpreterProxy.pop(argCount); return true; }, primitiveSetTransform: function(argCount) { if (argCount !== 3) return false; if (!this.currentFromStack(2)) return false; var modelViewMatrix = this.stackMatrix(1); var projectionMatrix = this.stackMatrix(0); if (!modelViewMatrix || !projectionMatrix) return false; DEBUG > 0 && console.log("B3DAccel: primitiveSetTransform", projectionMatrix, modelViewMatrix); OpenGL.glMatrixMode(GL.PROJECTION); OpenGL.glLoadMatrixf(projectionMatrix); OpenGL.glMatrixMode(GL.MODELVIEW); OpenGL.glLoadMatrixf(modelViewMatrix); this.interpreterProxy.pop(argCount); return true; }, primitiveSetLights: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; var lightArray = this.interpreterProxy.stackObjectValue(0); if (this.interpreterProxy.failed()) return false; if (!this.b3dxDisableLights(currentRenderer)) return false; if (!lightArray) return false; DEBUG > 1 && console.log("B3DAccel: UNIMPLEMENTED primitiveSetLights " + lightArray); var lightCount = lightArray.pointersSize(); for (var i = 0; i < lightCount; i++) { var light = this.fetchLightSource(i, lightArray); if (!this.b3dxLoadLight(currentRenderer, i, light)) return false; } this.interpreterProxy.pop(argCount); return true; }, primitiveSetMaterial: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; var material = this.stackMaterialValue(0); DEBUG > 0 && console.log("B3DAccel: primitiveSetMaterial", renderer, material); if (!material) { OpenGL.glMaterialfv(GL.FRONT, GL.AMBIENT, [0.2, 0.2, 0.2, 1.0]); OpenGL.glMaterialfv(GL.FRONT, GL.DIFFUSE, [0.8, 0.8, 0.8, 1.0]); OpenGL.glMaterialfv(GL.FRONT, GL.SPECULAR, [0.0, 0.0, 0.0, 1.0]); OpenGL.glMaterialfv(GL.FRONT, GL.EMISSION, [0.0, 0.0, 0.0, 1.0]); OpenGL.glMaterialf(GL.FRONT, GL.SHININESS, 0.0); OpenGL.glDisable(GL.TEXTURE_2D); } else { // 0: ambient, 4: diffuse, 8: specular, 12: emission, 16: shininess this.vm.warnOnce("B3DAccel: primitiveSetMaterial not fully implemented"); DEBUG > 1 && console.log("B3DAccel: UNIMPLEMENTED primitiveSetMaterial", material); } this.interpreterProxy.pop(argCount); return true; }, primitiveSwapRendererBuffers: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; DEBUG > 1 && console.log("B3DAccel: primitiveSwapRendererBuffers", currentRenderer); // let browser display the rendered frame this.interpreterProxy.vm.breakNow(); // tell the spinner we have been rendering this.primHandler.display.lastTick = this.vm.lastTick; if (DEBUG_WAIT) debugger; // wait after each frame this.interpreterProxy.pop(argCount); return true; }, primitiveTextureDepth: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; var depth = 32; DEBUG > 0 && console.log("B3DAccel: primitiveTextureDepth", depth); return this.primHandler.popNandPushIfOK(argCount + 1, depth); }, primitiveTextureGetColorMasks: function(argCount) { if (argCount !== 3) return false; if (!this.currentFromStack(2)) return false; var texture = this.interpreterProxy.stackIntegerValue(1); var array = this.interpreterProxy.stackObjectValue(0); if (array.pointersSize() !== 4) return false; var masks = [0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000]; for (var i = 0; i < 4; i++) { array.pointers[i] = this.interpreterProxy.positive32BitIntegerFor(masks[i]); } DEBUG > 0 && console.log("B3DAccel: primitiveTextureGetColorMasks", texture, masks); this.interpreterProxy.pop(argCount); return true; }, primitiveTextureByteSex: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; // return > 0 if MSB, = 0 if LSB, var byteSex = 0; DEBUG > 0 && console.log("B3DAccel: primitiveTextureByteSex", byteSex); return this.primHandler.popNandPushIfOK(argCount + 1, byteSex); }, primitiveTextureSurfaceHandle: function(argCount) { /* GL textures are not directly accessible */ return false; }, primitiveTextureUpload: function(argCount) { if (argCount !== 3) return false; if (!this.currentFromStack(2)) return false; var texture = this.interpreterProxy.stackIntegerValue(1); var form = this.interpreterProxy.stackObjectValue(0); if (form.pointersSize() < 4) return false; var bits = form.pointers[Squeak.Form_bits].words; var w = form.pointers[Squeak.Form_width]; var h = form.pointers[Squeak.Form_height]; var d = form.pointers[Squeak.Form_depth]; var ppw = 32 / d; if (!bits || bits.length !== (w + ppw - 1) / ppw * h) return false; DEBUG > 1 && console.log("B3DAccel: primitiveTextureUpload", texture, w, h, d, bits); var result = this.b3dxUploadTexture(texture, w, h, d, bits); if (!result) return false; this.interpreterProxy.pop(argCount); return true; }, b3dxCompositeTexture: function(texture, x, y, w, h, translucent) { DEBUG > 1 && console.log("B3DAccel: b3dxCompositeTexture", texture, x, y, w, h, translucent); if (!OpenGL.glIsTexture(texture)) return false; OpenGL.glMatrixMode(GL.MODELVIEW); OpenGL.glPushMatrix(); OpenGL.glLoadIdentity(); OpenGL.glMatrixMode(GL.PROJECTION); OpenGL.glPushMatrix(); OpenGL.glLoadIdentity(); var width = currentRenderer.viewport.w; var height = currentRenderer.viewport.h; OpenGL.glViewport(0, 0, width, height); OpenGL.glScaled(2.0/width, -2.0/height, 1.0); OpenGL.glTranslated(width*-0.5, height*-0.5, 0.0); //We haven't implemented glPushAttrib and glPopAttrib yet //OpenGL.glPushAttrib(GL.ALL_ATTRIB_BITS); // OpenGL.glShadeModel(GL.FLAT); // not implemented OpenGL.glEnable(GL.TEXTURE_2D); // OpenGL.glDisable(GL.COLOR_MATERIAL); // not implemented // OpenGL.glDisable(GL.DITHER); // OpenGL.glDisable(GL.LIGHTING); OpenGL.glDisable(GL.DEPTH_TEST); OpenGL.glDisable(GL.BLEND); OpenGL.glDisable(GL.CULL_FACE); OpenGL.glDepthMask(GL.FALSE); OpenGL.glColor4d(1.0, 1.0, 1.0, 1.0); if (translucent) { OpenGL.glEnable(GL.BLEND); OpenGL.glBlendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); } // subtract top and left position of canvas x -= currentRenderer.viewport.x; y -= currentRenderer.viewport.y; OpenGL.glBindTexture(GL.TEXTURE_2D, texture); OpenGL.glBegin(GL.QUADS); OpenGL.glTexCoord2d(0.0, 0.0); OpenGL.glVertex2i(x, y); OpenGL.glTexCoord2d(1.0, 0.0); OpenGL.glVertex2i(x+w, y); OpenGL.glTexCoord2d(1.0, 1.0); OpenGL.glVertex2i(x+w, y+h); OpenGL.glTexCoord2d(0.0, 1.0); OpenGL.glVertex2i(x, y+h); OpenGL.glEnd(); // instead of this ... // OpenGL.glPopAttrib(); // we do this: OpenGL.glDepthMask(GL.TRUE); OpenGL.glEnable(GL.DEPTH_TEST); OpenGL.glEnable(GL.CULL_FACE); OpenGL.glDisable(GL.BLEND); // OpenGL.glEnable(GL.COLOR_MATERIAL); // not implemented // OpenGL.glEnable(GL.DITHER); // not implemented OpenGL.glDisable(GL.TEXTURE_2D); // OpenGL.glShadeModel(GL.SMOOTH); // not implemented OpenGL.glPopMatrix(); OpenGL.glMatrixMode(GL.MODELVIEW); OpenGL.glPopMatrix(); return true; }, adjustCanvas: function(renderer) { var canvas = renderer.canvas; var sq = this.primHandler.display.css; var x = renderer.viewport.x; var y = renderer.viewport.y; var w = renderer.viewport.w; var h = renderer.viewport.h; canvas.width = w; canvas.height = h; canvas.style.left = (sq.left + x * sq.scale) + "px"; canvas.style.top = (sq.top + y * sq.scale) + "px"; canvas.style.width = (w * sq.scale) + "px"; canvas.style.height = (h * sq.scale) + "px"; var warning = renderer.warning; if (warning) { warning.style.left = (sq.left + x * sq.scale) + "px"; warning.style.top = (sq.top + y * sq.scale) + "px"; warning.style.width = (w * sq.scale) + "px"; warning.style.height = (h * sq.scale) + "px"; } }, b3dxSetViewport: function(renderer, x, y, w, h) { renderer.viewport = {x: x, y: y, w: w, h: h}; this.adjustCanvas(renderer); }, b3dxDisableLights: function(renderer) { OpenGL.glDisable(GL.LIGHTING); for (var i = 0; i < 8; i++) { OpenGL.glDisable(GL.LIGHT0 + i); } return true; }, b3dxLoadLight: function(renderer, index, light) { DEBUG > 0 && console.log("B3DAccel: b3dxLoadLight", renderer, index, light); return true; }, b3dxGetIntProperty: function(renderer, prop) { // switch (prop) { // case 1: /* backface culling */ // if (!glIsEnabled(GL_CULL_FACE)) return 0; // glGetIntegerv(GL_FRONT_FACE, & v); // if (v == GL_CW) return 1; // if (v == GL_CCW) return -1; // return 0; // case 2: /* polygon mode */ // glGetIntegerv(GL_POLYGON_MODE, & v); // ERROR_CHECK; // return v; // case 3: /* point size */ // glGetIntegerv(GL_POINT_SIZE, & v); // ERROR_CHECK; // return v; // case 4: /* line width */ // glGetIntegerv(GL_LINE_WIDTH, & v); // ERROR_CHECK; // return v; // case 5: /* blend enable */ // return glIsEnabled(GL_BLEND); // case 6: /* blend source factor */ // case 7: /* blend dest factor */ // if (prop == 6) // glGetIntegerv(GL_BLEND_SRC, & v); // else // glGetIntegerv(GL_BLEND_DST, & v); // ERROR_CHECK; // switch (v) { // case GL_ZERO: return 0; // case GL_ONE: return 1; // case GL_SRC_COLOR: return 2; // case GL_ONE_MINUS_SRC_COLOR: return 3; // case GL_DST_COLOR: return 4; // case GL_ONE_MINUS_DST_COLOR: return 5; // case GL_SRC_ALPHA: return 6; // case GL_ONE_MINUS_SRC_ALPHA: return 7; // case GL_DST_ALPHA: return 8; // case GL_ONE_MINUS_DST_ALPHA: return 9; // case GL_SRC_ALPHA_SATURATE: return 10; // default: return -1; // } // } return 0; }, b3dxUploadTexture: function(texture, w, h, d, bits) { if (!OpenGL.glIsTexture(texture)) return false; OpenGL.glBindTexture(GL.TEXTURE_2D, texture); OpenGL.glTexSubImage2D(GL.TEXTURE_2D, 0, 0, 0, w, h, GL.RGBA, GL.UNSIGNED_BYTE, bits.buffer); return true; }, fetchLightSource: function(index, lightArray) { var light = lightArray.pointers[index]; if (!light) return null; DEBUG > 0 && console.log("B3DAccel: fetchLightSource", index, light); return light; }, stackMatrix: function(stackIndex) { var m = this.interpreterProxy.stackObjectValue(stackIndex); if (!m.words || m.words.length !== 16) return null; return m.wordsAsFloat32Array(); }, stackMaterialValue: function(stackIndex) { var material = this.interpreterProxy.stackObjectValue(stackIndex); if (!material.words) return null; return material.wordsAsFloat32Array(); }, } } function registerB3DAcceleratorPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('B3DAcceleratorPlugin', B3DAcceleratorPlugin()); } else self.setTimeout(registerB3DAcceleratorPlugin, 100); }; registerB3DAcceleratorPlugin();