package kha; import haxe.io.Bytes; import js.Browser; import js.lib.Uint8Array; import js.lib.Uint16Array; import js.lib.Float32Array; import js.html.VideoElement; import js.html.webgl.GL; import js.html.webgl.Framebuffer; import js.html.webgl.Renderbuffer; import js.html.webgl.Texture; import kha.graphics4.TextureFormat; import kha.graphics4.DepthStencilFormat; import kha.js.graphics4.Graphics; class WebGLImage extends Image { public var image: Dynamic; public var video: VideoElement; static var context: js.html.CanvasRenderingContext2D; var data: js.html.ImageData; var myWidth: Int; var myHeight: Int; var myFormat: TextureFormat; var renderTarget: Bool; var samples: Int; public var frameBuffer: Framebuffer = null; public var renderBuffer: Renderbuffer = null; public var texture: Texture = null; public var depthTexture: Texture = null; public var MSAAFrameBuffer: Framebuffer = null; var MSAAColorBuffer: Renderbuffer; var MSAADepthBuffer: Renderbuffer; var graphics1: kha.graphics1.Graphics; var graphics2: kha.graphics2.Graphics; var graphics4: kha.graphics4.Graphics; var depthStencilFormat: DepthStencilFormat; var readable: Bool; // WebGL2 constants static inline var GL_RGBA16F = 0x881A; static inline var GL_RGBA32F = 0x8814; static inline var GL_R16F = 0x822D; static inline var GL_R32F = 0x822E; static inline var GL_RED = 0x1903; static inline var GL_DEPTH_COMPONENT24 = 0x81A6; static inline var GL_DEPTH24_STENCIL8 = 0x88F0; static inline var GL_DEPTH32F_STENCIL8 = 0x8CAD; static var canvas: js.html.CanvasElement; public static function init() { if (context == null) { // create only once canvas = Browser.document.createCanvasElement(); if (canvas != null) { context = canvas.getContext("2d"); canvas.width = 4096; canvas.height = 4096; context.globalCompositeOperation = "copy"; } } } public function new(width: Int, height: Int, format: TextureFormat, renderTarget: Bool, depthStencilFormat: DepthStencilFormat, samples: Int, readable: Bool) { myWidth = width; myHeight = height; myFormat = format; this.renderTarget = renderTarget; this.samples = samples; this.readable = readable; image = null; video = null; this.depthStencilFormat = depthStencilFormat; init(); if (renderTarget) createTexture(); } override function get_g1(): kha.graphics1.Graphics { if (graphics1 == null) { graphics1 = new kha.graphics2.Graphics1(this); } return graphics1; } override function get_g2(): kha.graphics2.Graphics { if (graphics2 == null) { graphics2 = new kha.js.graphics4.Graphics2(this); } return graphics2; } override function get_g4(): kha.graphics4.Graphics { if (graphics4 == null) { graphics4 = new Graphics(this); } return graphics4; } override function get_width(): Int { return myWidth; } override function get_height(): Int { return myHeight; } override function get_format(): TextureFormat { return myFormat; } override function get_realWidth(): Int { return myWidth; } override function get_realHeight(): Int { return myHeight; } override function get_stride(): Int { return formatByteSize(myFormat) * width; } override public function isOpaque(x: Int, y: Int): Bool { if (data == null) { if (context == null) return true; else createImageData(); } return (data.data[y * Std.int(image.width) * 4 + x * 4 + 3] != 0); } override public function at(x: Int, y: Int): Color { if (bytes != null) { var r = bytes.get(y * width * 4 + x * 4); var g = bytes.get(y * width * 4 + x * 4 + 1); var b = bytes.get(y * width * 4 + x * 4 + 2); var a = bytes.get(y * width * 4 + x * 4 + 3); return Color.fromValue((a << 24) | (r << 16) | (g << 8) | b); } else { if (data == null) { if (context == null) return Color.Black; else createImageData(); } var r = data.data[y * width * 4 + x * 4]; var g = data.data[y * width * 4 + x * 4 + 1]; var b = data.data[y * width * 4 + x * 4 + 2]; var a = data.data[y * width * 4 + x * 4 + 3]; return Color.fromValue((a << 24) | (r << 16) | (g << 8) | b); } } function createImageData() { if (Std.isOfType(image, Uint8Array)) { data = new js.html.ImageData(new js.lib.Uint8ClampedArray(image.buffer), this.width, this.height); } else { if (this.width > canvas.width || this.height > canvas.height) { var cw = canvas.width; var ch = canvas.height; while (this.width > cw || this.height > ch) { cw *= 2; ch *= 2; } canvas.width = cw; canvas.height = ch; } context.strokeStyle = "rgba(0,0,0,0)"; context.fillStyle = "rgba(0,0,0,0)"; context.fillRect(0, 0, image.width, image.height); context.drawImage(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height); data = context.getImageData(0, 0, image.width, image.height); } } static function upperPowerOfTwo(v: Int): Int { v--; v |= v >>> 1; v |= v >>> 2; v |= v >>> 4; v |= v >>> 8; v |= v >>> 16; v++; return v; } public function createTexture(): Void { if (SystemImpl.gl == null) return; texture = SystemImpl.gl.createTexture(); // texture.image = image; SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture); // Sys.gl.pixelStorei(Sys.gl.UNPACK_FLIP_Y_WEBGL, true); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); if (renderTarget) { frameBuffer = SystemImpl.gl.createFramebuffer(); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); switch (myFormat) { case DEPTH16: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL.DEPTH_COMPONENT16 : GL.DEPTH_COMPONENT, realWidth, realHeight, 0, GL.DEPTH_COMPONENT, GL.UNSIGNED_SHORT, null); case RGBA128: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_RGBA32F : GL.RGBA, realWidth, realHeight, 0, GL.RGBA, GL.FLOAT, null); case RGBA64: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_RGBA16F : GL.RGBA, realWidth, realHeight, 0, GL.RGBA, SystemImpl.halfFloat.HALF_FLOAT_OES, null); case RGBA32: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, realWidth, realHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null); case A32: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_R32F : GL.ALPHA, realWidth, realHeight, 0, SystemImpl.gl2 ? GL_RED : GL.ALPHA, GL.FLOAT, null); case A16: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_R16F : GL.ALPHA, realWidth, realHeight, 0, SystemImpl.gl2 ? GL_RED : GL.ALPHA, SystemImpl.halfFloat.HALF_FLOAT_OES, null); default: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, realWidth, realHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null); } if (myFormat == DEPTH16) { SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST); SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.TEXTURE_2D, texture, 0); // Some WebGL implementations throw incomplete framebuffer error, create color attachment if (!SystemImpl.gl2) { var colortex = SystemImpl.gl.createTexture(); SystemImpl.gl.bindTexture(GL.TEXTURE_2D, colortex); SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, realWidth, realHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, null); SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, colortex, 0); SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture); } } else { if (samples > 1 && SystemImpl.gl2) { MSAAFrameBuffer = SystemImpl.gl.createFramebuffer(); MSAAColorBuffer = SystemImpl.gl.createRenderbuffer(); SystemImpl.gl.bindRenderbuffer(GL.RENDERBUFFER, MSAAColorBuffer); var MSAAFormat = switch (myFormat) { case RGBA128: untyped SystemImpl.gl.RGBA32F; case RGBA64: untyped SystemImpl.gl.RGBA16F; case RGBA32: untyped SystemImpl.gl.RGBA8; case A32: GL_R32F; case A16: GL_R16F; default: untyped SystemImpl.gl.RGBA8; }; untyped SystemImpl.gl.renderbufferStorageMultisample(GL.RENDERBUFFER, samples, MSAAFormat, realWidth, realHeight); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); SystemImpl.gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.RENDERBUFFER, MSAAColorBuffer); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, MSAAFrameBuffer); } SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, texture, 0); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); } initDepthStencilBuffer(depthStencilFormat); var e = SystemImpl.gl.checkFramebufferStatus(GL.FRAMEBUFFER); if (e != GL.FRAMEBUFFER_COMPLETE) { trace("checkframebufferStatus error " + e); } SystemImpl.gl.bindRenderbuffer(GL.RENDERBUFFER, null); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); } else if (video != null) { SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, video); } else { switch (myFormat) { case RGBA128: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_RGBA32F : GL.RGBA, myWidth, myHeight, 0, GL.RGBA, GL.FLOAT, image); case RGBA64: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_RGBA16F : GL.RGBA, myWidth, myHeight, 0, GL.RGBA, SystemImpl.halfFloat.HALF_FLOAT_OES, image); case RGBA32: if (Std.isOfType(image, Uint8Array)) { SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, myWidth, myHeight, 0, GL.RGBA, GL.UNSIGNED_BYTE, image); } else { SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, image); } case A32: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_R32F : GL.ALPHA, myWidth, myHeight, 0, SystemImpl.gl2 ? GL_RED : GL.ALPHA, GL.FLOAT, image); case A16: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_R16F : GL.ALPHA, myWidth, myHeight, 0, SystemImpl.gl2 ? GL_RED : GL.ALPHA, SystemImpl.halfFloat.HALF_FLOAT_OES, image); case L8: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.LUMINANCE, myWidth, myHeight, 0, GL.LUMINANCE, GL.UNSIGNED_BYTE, image); default: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, image); } } SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null); } function initDepthStencilBuffer(depthStencilFormat: DepthStencilFormat) { switch (depthStencilFormat) { case NoDepthAndStencil: case DepthOnly, Depth16: { if (SystemImpl.depthTexture == null) { renderBuffer = SystemImpl.gl.createRenderbuffer(); SystemImpl.gl.bindRenderbuffer(GL.RENDERBUFFER, renderBuffer); SystemImpl.gl.renderbufferStorage(GL.RENDERBUFFER, GL.DEPTH_COMPONENT16, realWidth, realHeight); SystemImpl.gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, renderBuffer); } else { depthTexture = SystemImpl.gl.createTexture(); SystemImpl.gl.bindTexture(GL.TEXTURE_2D, depthTexture); if (depthStencilFormat == DepthOnly) SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_DEPTH_COMPONENT24 : GL.DEPTH_COMPONENT, realWidth, realHeight, 0, GL.DEPTH_COMPONENT, GL.UNSIGNED_INT, null); else SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL.DEPTH_COMPONENT16 : GL.DEPTH_COMPONENT, realWidth, realHeight, 0, GL.DEPTH_COMPONENT, GL.UNSIGNED_SHORT, null); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); if (samples > 1 && SystemImpl.gl2) { MSAADepthBuffer = SystemImpl.gl.createRenderbuffer(); SystemImpl.gl.bindRenderbuffer(GL.RENDERBUFFER, MSAADepthBuffer); if (depthStencilFormat == DepthOnly) untyped SystemImpl.gl.renderbufferStorageMultisample(GL.RENDERBUFFER, samples, GL_DEPTH_COMPONENT24, realWidth, realHeight); else untyped SystemImpl.gl.renderbufferStorageMultisample(GL.RENDERBUFFER, samples, GL.DEPTH_COMPONENT16, realWidth, realHeight); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); SystemImpl.gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, MSAADepthBuffer); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, MSAAFrameBuffer); } SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.TEXTURE_2D, depthTexture, 0); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); } } case DepthAutoStencilAuto, Depth24Stencil8, Depth32Stencil8: if (SystemImpl.depthTexture == null) { renderBuffer = SystemImpl.gl.createRenderbuffer(); SystemImpl.gl.bindRenderbuffer(GL.RENDERBUFFER, renderBuffer); SystemImpl.gl.renderbufferStorage(GL.RENDERBUFFER, GL.DEPTH_STENCIL, realWidth, realHeight); SystemImpl.gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.DEPTH_STENCIL_ATTACHMENT, GL.RENDERBUFFER, renderBuffer); } else { depthTexture = SystemImpl.gl.createTexture(); SystemImpl.gl.bindTexture(GL.TEXTURE_2D, depthTexture); SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_DEPTH24_STENCIL8 : GL.DEPTH_STENCIL, realWidth, realHeight, 0, GL.DEPTH_STENCIL, SystemImpl.depthTexture.UNSIGNED_INT_24_8_WEBGL, null); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); if (samples > 1 && SystemImpl.gl2) { MSAADepthBuffer = SystemImpl.gl.createRenderbuffer(); SystemImpl.gl.bindRenderbuffer(GL.RENDERBUFFER, MSAADepthBuffer); untyped SystemImpl.gl.renderbufferStorageMultisample(GL.RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, realWidth, realHeight); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); SystemImpl.gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.DEPTH_STENCIL_ATTACHMENT, GL.RENDERBUFFER, MSAADepthBuffer); SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, MSAAFrameBuffer); } SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_STENCIL_ATTACHMENT, GL.TEXTURE_2D, depthTexture, 0); } } } public function set(stage: Int): Void { SystemImpl.gl.activeTexture(GL.TEXTURE0 + stage); SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture); if (video != null) SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, video); } public function setDepth(stage: Int): Void { SystemImpl.gl.activeTexture(GL.TEXTURE0 + stage); SystemImpl.gl.bindTexture(GL.TEXTURE_2D, depthTexture); } override public function setDepthStencilFrom(image: Image): Void { depthTexture = cast(image, WebGLImage).depthTexture; SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.TEXTURE_2D, depthTexture, 0); if (samples > 1 && SystemImpl.gl2) { MSAADepthBuffer = cast(image, WebGLImage).MSAADepthBuffer; SystemImpl.gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, MSAADepthBuffer); } } static function formatByteSize(format: TextureFormat): Int { return switch (format) { case RGBA32: 4; case L8: 1; case RGBA128: 16; case DEPTH16: 2; case RGBA64: 8; case A32: 4; case A16: 2; default: 4; } } public function bytesToArray(bytes: Bytes): js.lib.ArrayBufferView { return switch (myFormat) { case RGBA32, L8: new Uint8Array(bytes.getData()); case RGBA128, RGBA64, A32, A16: new Float32Array(bytes.getData()); default: new Uint8Array(bytes.getData()); } } public var bytes: Bytes; override public function lock(level: Int = 0): Bytes { bytes = Bytes.alloc(formatByteSize(myFormat) * width * height); return bytes; } override public function unlock(): Void { data = null; image = null; if (SystemImpl.gl != null) { texture = SystemImpl.gl.createTexture(); // texture.image = image; SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture); // Sys.gl.pixelStorei(Sys.gl.UNPACK_FLIP_Y_WEBGL, true); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); switch (myFormat) { case L8: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.LUMINANCE, width, height, 0, GL.LUMINANCE, GL.UNSIGNED_BYTE, bytesToArray(bytes)); if (SystemImpl.ie && SystemImpl.gl.getError() == 1282) { // no LUMINANCE support in IE11 var rgbaBytes = Bytes.alloc(width * height * 4); for (y in 0...height) for (x in 0...width) { var value = bytes.get(y * width + x); rgbaBytes.set(y * width * 4 + x * 4 + 0, value); rgbaBytes.set(y * width * 4 + x * 4 + 1, value); rgbaBytes.set(y * width * 4 + x * 4 + 2, value); rgbaBytes.set(y * width * 4 + x * 4 + 3, 255); } SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, width, height, 0, GL.RGBA, GL.UNSIGNED_BYTE, bytesToArray(rgbaBytes)); } case RGBA128: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_RGBA32F : GL.RGBA, width, height, 0, GL.RGBA, GL.FLOAT, bytesToArray(bytes)); case RGBA64: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_RGBA16F : GL.RGBA, width, height, 0, GL.RGBA, SystemImpl.halfFloat.HALF_FLOAT_OES, bytesToArray(bytes)); case A32: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_R32F : GL.ALPHA, width, height, 0, SystemImpl.gl2 ? GL_RED : GL.ALPHA, GL.FLOAT, bytesToArray(bytes)); case A16: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, SystemImpl.gl2 ? GL_R16F : GL.ALPHA, width, height, 0, SystemImpl.gl2 ? GL_RED : GL.ALPHA, SystemImpl.halfFloat.HALF_FLOAT_OES, bytesToArray(bytes)); case RGBA32: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, width, height, 0, GL.RGBA, GL.UNSIGNED_BYTE, bytesToArray(bytes)); default: SystemImpl.gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, width, height, 0, GL.RGBA, GL.UNSIGNED_BYTE, bytesToArray(bytes)); } SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null); if (!readable) { bytes = null; } } } var pixels: js.lib.ArrayBufferView = null; override public function getPixels(): Bytes { if (frameBuffer == null) return null; if (pixels == null) { switch (myFormat) { case RGBA128, A32: pixels = new Float32Array(Std.int(formatByteSize(myFormat) / 4) * width * height); case RGBA64, A16: pixels = new Uint16Array(Std.int(formatByteSize(myFormat) / 2) * width * height); case RGBA32, L8: pixels = new Uint8Array(formatByteSize(myFormat) * width * height); default: pixels = new Uint8Array(formatByteSize(myFormat) * width * height); } } SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, frameBuffer); switch (myFormat) { case RGBA128: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, GL.RGBA, GL.FLOAT, pixels); case RGBA64: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, GL.RGBA, SystemImpl.halfFloat.HALF_FLOAT_OES, pixels); case RGBA32: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, GL.RGBA, GL.UNSIGNED_BYTE, pixels); case A32: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, SystemImpl.gl2 ? GL_RED : GL.ALPHA, GL.FLOAT, pixels); case A16: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, SystemImpl.gl2 ? GL_RED : GL.ALPHA, SystemImpl.halfFloat.HALF_FLOAT_OES, pixels); case L8: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, SystemImpl.gl2 ? GL_RED : GL.ALPHA, GL.UNSIGNED_BYTE, pixels); default: SystemImpl.gl.readPixels(0, 0, myWidth, myHeight, GL.RGBA, GL.UNSIGNED_BYTE, pixels); } return Bytes.ofData(pixels.buffer); } override public function unload(): Void { if (texture != null) SystemImpl.gl.deleteTexture(texture); if (depthTexture != null) SystemImpl.gl.deleteTexture(depthTexture); if (frameBuffer != null) SystemImpl.gl.deleteFramebuffer(frameBuffer); if (renderBuffer != null) SystemImpl.gl.deleteRenderbuffer(renderBuffer); if (MSAAFrameBuffer != null) SystemImpl.gl.deleteFramebuffer(MSAAFrameBuffer); if (MSAAColorBuffer != null) SystemImpl.gl.deleteRenderbuffer(MSAAColorBuffer); if (MSAADepthBuffer != null) SystemImpl.gl.deleteRenderbuffer(MSAADepthBuffer); } override public function generateMipmaps(levels: Int): Void { // WebGL requires to generate all mipmaps down to 1x1 size, ignoring levels for now SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture); SystemImpl.gl.generateMipmap(GL.TEXTURE_2D); } override public function setMipmaps(mipmaps: Array): Void { // Similar to generateMipmaps, specify all the levels down to 1x1 size SystemImpl.gl.bindTexture(GL.TEXTURE_2D, texture); if (myFormat == TextureFormat.RGBA128) { for (i in 0...mipmaps.length) { SystemImpl.gl.texImage2D(GL.TEXTURE_2D, i + 1, SystemImpl.gl2 ? GL_RGBA32F : GL.RGBA, mipmaps[i].width, mipmaps[i].height, 0, GL.RGBA, GL.FLOAT, cast(mipmaps[i], WebGLImage).image); } } else if (myFormat == TextureFormat.RGBA64) { for (i in 0...mipmaps.length) { SystemImpl.gl.texImage2D(GL.TEXTURE_2D, i + 1, SystemImpl.gl2 ? GL_RGBA16F : GL.RGBA, mipmaps[i].width, mipmaps[i].height, 0, GL.RGBA, SystemImpl.halfFloat.HALF_FLOAT_OES, cast(mipmaps[i], WebGLImage).image); } } else { for (i in 0...mipmaps.length) { SystemImpl.gl.texImage2D(GL.TEXTURE_2D, i + 1, GL.RGBA, GL.RGBA, GL.UNSIGNED_BYTE, cast(mipmaps[i], WebGLImage).image); } } } }