diff --git a/Kha/Backends/HTML5/kha/SystemImpl.hx b/Kha/Backends/HTML5/kha/SystemImpl.hx index 7feee51..f2a7f7c 100644 --- a/Kha/Backends/HTML5/kha/SystemImpl.hx +++ b/Kha/Backends/HTML5/kha/SystemImpl.hx @@ -227,7 +227,7 @@ class SystemImpl { } static inline var maxGamepads: Int = 4; - static var frame: Framebuffer; + public static var frame: Framebuffer; static var keyboard: Keyboard = null; static var mouse: kha.input.Mouse; static var surface: Surface; @@ -388,7 +388,8 @@ class SystemImpl { { alpha: false, antialias: options.framebuffer.samplesPerPixel > 1, - stencil: true + stencil: true, + xrCompatible: true }); // preserveDrawingBuffer: true } ); Warning: preserveDrawingBuffer can cause huge performance issues on mobile browsers SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); @@ -417,7 +418,8 @@ class SystemImpl { { alpha: false, antialias: options.framebuffer.samplesPerPixel > 1, - stencil: true + stencil: true, + xrCompatible: true }); // preserveDrawingBuffer: true } ); WARNING: preserveDrawingBuffer causes huge performance issues (on mobile browser)! SystemImpl.gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); SystemImpl.gl.getExtension("OES_texture_float"); @@ -547,6 +549,12 @@ class SystemImpl { ]; function animate(timestamp) { + if (untyped Browser.window._khaSkipWindowRender == true) { + if (requestAnimationFrame != null) + requestAnimationFrame(animate); + return; + } + if (requestAnimationFrame == null) Browser.window.setTimeout(animate, 1000.0 / 60.0); else diff --git a/Kha/Backends/HTML5/kha/js/graphics4/Graphics.hx b/Kha/Backends/HTML5/kha/js/graphics4/Graphics.hx index dcaf6f9..357ac5c 100644 --- a/Kha/Backends/HTML5/kha/js/graphics4/Graphics.hx +++ b/Kha/Backends/HTML5/kha/js/graphics4/Graphics.hx @@ -1,738 +1,741 @@ -package kha.js.graphics4; - -import js.html.webgl.GL2; -import kha.graphics4.StencilValue; -import kha.arrays.Float32Array; -import kha.arrays.Int32Array; -import js.html.webgl.GL; -import kha.graphics4.BlendingFactor; -import kha.graphics4.BlendingOperation; -import kha.graphics4.CompareMode; -import kha.graphics4.CubeMap; -import kha.graphics4.CullMode; -import kha.graphics4.IndexBuffer; -import kha.graphics4.MipMapFilter; -import kha.graphics4.PipelineState; -import kha.graphics4.StencilAction; -import kha.graphics4.TextureAddressing; -import kha.graphics4.TextureFilter; -import kha.graphics4.Usage; -import kha.graphics4.VertexBuffer; -import kha.graphics4.VertexStructure; -import kha.Image; -import kha.math.FastMatrix3; -import kha.math.FastMatrix4; -import kha.math.FastVector2; -import kha.math.FastVector3; -import kha.math.FastVector4; -import kha.WebGLImage; - -class Graphics implements kha.graphics4.Graphics { - var currentPipeline: PipelineState = null; - var depthTest: Bool = false; - var depthMask: Bool = false; - var colorMaskRed: Bool = true; - var colorMaskGreen: Bool = true; - var colorMaskBlue: Bool = true; - var colorMaskAlpha: Bool = true; - var indicesCount: Int; - var renderTarget: Canvas; - var renderTargetFrameBuffer: Dynamic; - var renderTargetMSAA: Dynamic; - var renderTargetTexture: Dynamic; - var isCubeMap: Bool = false; - var isDepthAttachment: Bool = false; - var instancedExtension: Dynamic; - var blendMinMaxExtension: Dynamic; - - static var current: Graphics = null; - static var useVertexAttributes: Int = 0; - - public function new(renderTarget: Canvas = null) { - this.renderTarget = renderTarget; - init(); - if (SystemImpl.gl2) { - instancedExtension = true; - } - else { - instancedExtension = SystemImpl.gl.getExtension("ANGLE_instanced_arrays"); - blendMinMaxExtension = SystemImpl.gl.getExtension("EXT_blend_minmax"); - } - } - - function init() { - if (renderTarget == null) - return; - isCubeMap = Std.isOfType(renderTarget, CubeMap); - if (isCubeMap) { - var cubeMap: CubeMap = cast(renderTarget, CubeMap); - renderTargetFrameBuffer = cubeMap.frameBuffer; - renderTargetTexture = cubeMap.texture; - isDepthAttachment = cubeMap.isDepthAttachment; - } - else { - var image: WebGLImage = cast(renderTarget, WebGLImage); - renderTargetFrameBuffer = image.frameBuffer; - renderTargetMSAA = image.MSAAFrameBuffer; - renderTargetTexture = image.texture; - } - } - - public function begin(additionalRenderTargets: Array = null): Void { - if (current == null) { - current = this; - } - else { - throw "End before you begin"; - } - - SystemImpl.gl.enable(GL.BLEND); - SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); - if (renderTarget == null) { - SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); - SystemImpl.gl.viewport(0, 0, System.windowWidth(), System.windowHeight()); - } - else { - SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, renderTargetFrameBuffer); - // if (isCubeMap) SystemImpl.gl.framebufferTexture(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_CUBE_MAP, renderTargetTexture, 0); // Layered - SystemImpl.gl.viewport(0, 0, renderTarget.width, renderTarget.height); - if (additionalRenderTargets != null) { - SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL, GL.TEXTURE_2D, renderTargetTexture, 0); - for (i in 0...additionalRenderTargets.length) { - SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL + i + 1, GL.TEXTURE_2D, - cast(additionalRenderTargets[i], WebGLImage).texture, 0); - } - var attachments = [SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL]; - for (i in 0...additionalRenderTargets.length) { - attachments.push(SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL + i + 1); - } - SystemImpl.gl2 ? untyped SystemImpl.gl.drawBuffers(attachments) : SystemImpl.drawBuffers.drawBuffersWEBGL(attachments); - } - } - } - - public function beginFace(face: Int): Void { - if (current == null) { - current = this; - } - else { - throw "End before you begin"; - } - - SystemImpl.gl.enable(GL.BLEND); - SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); - SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, renderTargetFrameBuffer); - SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, isDepthAttachment ? GL.DEPTH_ATTACHMENT : GL.COLOR_ATTACHMENT0, - GL.TEXTURE_CUBE_MAP_POSITIVE_X + face, renderTargetTexture, 0); - SystemImpl.gl.viewport(0, 0, renderTarget.width, renderTarget.height); - } - - public function beginEye(eye: Int): Void { - if (current == null) { - current = this; - } - else { - throw "End before you begin"; - } - - SystemImpl.gl.enable(GL.BLEND); - SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); - SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); - if (eye == 0) { - SystemImpl.gl.viewport(0, 0, Std.int(System.windowWidth() * 0.5), System.windowHeight()); - } - else { - SystemImpl.gl.viewport(Std.int(System.windowWidth() * 0.5), 0, Std.int(System.windowWidth() * 0.5), System.windowHeight()); - } - } - - public function end(): Void { - if (current == this) { - current = null; - } - else { - throw "Begin before you end"; - } - - if (renderTargetMSAA != null) { - untyped SystemImpl.gl.bindFramebuffer(SystemImpl.gl.READ_FRAMEBUFFER, renderTargetFrameBuffer); - untyped SystemImpl.gl.bindFramebuffer(SystemImpl.gl.DRAW_FRAMEBUFFER, renderTargetMSAA); - untyped SystemImpl.gl.blitFramebuffer(0, 0, renderTarget.width, renderTarget.height, 0, 0, renderTarget.width, renderTarget.height, - GL.COLOR_BUFFER_BIT, GL.NEAREST); - } - #if (debug || kha_debug_html5) - var error = SystemImpl.gl.getError(); - switch (error) { - case GL.NO_ERROR: - - case GL.INVALID_ENUM: - trace("WebGL error: Invalid enum"); - case GL.INVALID_VALUE: - trace("WebGL error: Invalid value"); - case GL.INVALID_OPERATION: - trace("WebGL error: Invalid operation"); - case GL.INVALID_FRAMEBUFFER_OPERATION: - trace("WebGL error: Invalid framebuffer operation"); - case GL.OUT_OF_MEMORY: - trace("WebGL error: Out of memory"); - case GL.CONTEXT_LOST_WEBGL: - trace("WebGL error: Context lost"); - default: - trace("Unknown WebGL error"); - } - #end - } - - public function flush(): Void {} - - public function vsynced(): Bool { - return true; - } - - public function refreshRate(): Int { - return 60; - } - - public function clear(?color: Color, ?depth: Float, ?stencil: Int): Void { - var clearMask: Int = 0; - if (color != null) { - clearMask |= GL.COLOR_BUFFER_BIT; - SystemImpl.gl.colorMask(true, true, true, true); - SystemImpl.gl.clearColor(color.R, color.G, color.B, color.A); - } - if (depth != null) { - clearMask |= GL.DEPTH_BUFFER_BIT; - SystemImpl.gl.enable(GL.DEPTH_TEST); - SystemImpl.gl.depthMask(true); - SystemImpl.gl.clearDepth(depth); - } - if (stencil != null) { - clearMask |= GL.STENCIL_BUFFER_BIT; - SystemImpl.gl.enable(GL.STENCIL_TEST); - SystemImpl.gl.stencilMask(0xff); - SystemImpl.gl.clearStencil(stencil); - } - SystemImpl.gl.clear(clearMask); - SystemImpl.gl.colorMask(colorMaskRed, colorMaskGreen, colorMaskBlue, colorMaskAlpha); - if (depthTest) { - SystemImpl.gl.enable(GL.DEPTH_TEST); - } - else { - SystemImpl.gl.disable(GL.DEPTH_TEST); - } - SystemImpl.gl.depthMask(depthMask); - } - - public function viewport(x: Int, y: Int, width: Int, height: Int): Void { - if (renderTarget == null) { - SystemImpl.gl.viewport(x, System.windowHeight(0) - y - height, width, height); - } - else { - SystemImpl.gl.viewport(x, y, width, height); - } - } - - public function scissor(x: Int, y: Int, width: Int, height: Int): Void { - SystemImpl.gl.enable(GL.SCISSOR_TEST); - if (renderTarget == null) { - SystemImpl.gl.scissor(x, System.windowHeight(0) - y - height, width, height); - } - else { - SystemImpl.gl.scissor(x, y, width, height); - } - } - - public function disableScissor(): Void { - SystemImpl.gl.disable(GL.SCISSOR_TEST); - } - - public function setDepthMode(write: Bool, mode: CompareMode): Void { - switch (mode) { - case Always: - write ? SystemImpl.gl.enable(GL.DEPTH_TEST) : SystemImpl.gl.disable(GL.DEPTH_TEST); - depthTest = write; - SystemImpl.gl.depthFunc(GL.ALWAYS); - case Never: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.NEVER); - case Equal: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.EQUAL); - case NotEqual: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.NOTEQUAL); - case Less: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.LESS); - case LessEqual: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.LEQUAL); - case Greater: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.GREATER); - case GreaterEqual: - SystemImpl.gl.enable(GL.DEPTH_TEST); - depthTest = true; - SystemImpl.gl.depthFunc(GL.GEQUAL); - } - SystemImpl.gl.depthMask(write); - depthMask = write; - } - - static function getBlendFunc(factor: BlendingFactor): Int { - switch (factor) { - case BlendZero, Undefined: - return GL.ZERO; - case BlendOne: - return GL.ONE; - case SourceAlpha: - return GL.SRC_ALPHA; - case DestinationAlpha: - return GL.DST_ALPHA; - case InverseSourceAlpha: - return GL.ONE_MINUS_SRC_ALPHA; - case InverseDestinationAlpha: - return GL.ONE_MINUS_DST_ALPHA; - case SourceColor: - return GL.SRC_COLOR; - case DestinationColor: - return GL.DST_COLOR; - case InverseSourceColor: - return GL.ONE_MINUS_SRC_COLOR; - case InverseDestinationColor: - return GL.ONE_MINUS_DST_COLOR; - } - } - - static function getBlendOp(op: BlendingOperation): Int { - switch (op) { - case Add: - return GL.FUNC_ADD; - case Subtract: - return GL.FUNC_SUBTRACT; - case ReverseSubtract: - return GL.FUNC_REVERSE_SUBTRACT; - case Min: - return 0x8007; - case Max: - return 0x8008; - } - } - - public function setBlendingMode(source: BlendingFactor, destination: BlendingFactor, operation: BlendingOperation, alphaSource: BlendingFactor, - alphaDestination: BlendingFactor, alphaOperation: BlendingOperation): Void { - if (source == BlendOne && destination == BlendZero) { - SystemImpl.gl.disable(GL.BLEND); - } - else { - SystemImpl.gl.enable(GL.BLEND); - SystemImpl.gl.blendFuncSeparate(getBlendFunc(source), getBlendFunc(destination), getBlendFunc(alphaSource), getBlendFunc(alphaDestination)); - SystemImpl.gl.blendEquationSeparate(getBlendOp(operation), getBlendOp(alphaOperation)); - } - } - - public function createVertexBuffer(vertexCount: Int, structure: VertexStructure, usage: Usage, canRead: Bool = false): kha.graphics4.VertexBuffer { - return new VertexBuffer(vertexCount, structure, usage); - } - - public function setVertexBuffer(vertexBuffer: kha.graphics4.VertexBuffer): Void { - for (i in 0...useVertexAttributes) { - SystemImpl.gl.disableVertexAttribArray(i); - } - useVertexAttributes = vertexBuffer.set(0); - } - - public function setVertexBuffers(vertexBuffers: Array): Void { - for (i in 0...useVertexAttributes) { - SystemImpl.gl.disableVertexAttribArray(i); - } - var offset: Int = 0; - for (vertexBuffer in vertexBuffers) { - offset += vertexBuffer.set(offset); - } - useVertexAttributes = offset; - } - - public function createIndexBuffer(indexCount: Int, usage: Usage, canRead: Bool = false): kha.graphics4.IndexBuffer { - return new IndexBuffer(indexCount, usage); - } - - public function setIndexBuffer(indexBuffer: kha.graphics4.IndexBuffer): Void { - indicesCount = indexBuffer.count(); - indexBuffer.set(); - } - - // public function maxTextureSize(): Int { - // return Sys.gl == null ? 8192 : Sys.gl.getParameter(Sys.gl.MAX_TEXTURE_SIZE); - // } - // public function supportsNonPow2Textures(): Bool { - // return false; - // } - - public function setTexture(stage: kha.graphics4.TextureUnit, texture: kha.Image): Void { - if (texture == null) { - SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast stage : TextureUnit).value); - SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null); - } - else { - cast(texture, WebGLImage).set((cast stage : TextureUnit).value); - } - } - - public function setTextureDepth(stage: kha.graphics4.TextureUnit, texture: kha.Image): Void { - cast(texture, WebGLImage).setDepth((cast stage : TextureUnit).value); - } - - public function setTextureArray(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void { - // not implemented yet. - } - - public function setVideoTexture(unit: kha.graphics4.TextureUnit, texture: kha.Video): Void { - if (texture == null) { - SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast unit : TextureUnit).value); - SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null); - } - else { - cast((cast texture : kha.js.Video).texture, WebGLImage).set((cast unit : TextureUnit).value); - } - } - - public function setImageTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {} - - public function setTextureParameters(texunit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing, - minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void { - SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast texunit : TextureUnit).value); - - switch (uAddressing) { - case Clamp: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); - case Repeat: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.REPEAT); - case Mirror: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.MIRRORED_REPEAT); - } - - switch (vAddressing) { - case Clamp: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); - case Repeat: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.REPEAT); - case Mirror: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.MIRRORED_REPEAT); - } - - switch (minificationFilter) { - case PointFilter: - switch (mipmapFilter) { - case NoMipFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST); - case PointMipFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST_MIPMAP_NEAREST); - case LinearMipFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST_MIPMAP_LINEAR); - } - case LinearFilter, AnisotropicFilter: - switch (mipmapFilter) { - case NoMipFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); - case PointMipFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_NEAREST); - case LinearMipFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_LINEAR); - } - if (minificationFilter == AnisotropicFilter) { - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, SystemImpl.anisotropicFilter.TEXTURE_MAX_ANISOTROPY_EXT, 4); - } - } - - switch (magnificationFilter) { - case PointFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST); - case LinearFilter, AnisotropicFilter: - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); - } - } - - public function setTexture3DParameters(texunit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing, - wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {} - - public function setTextureCompareMode(texunit: kha.graphics4.TextureUnit, enabled: Bool) { - if (enabled) { - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL2.TEXTURE_COMPARE_MODE, GL2.COMPARE_REF_TO_TEXTURE); - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL2.TEXTURE_COMPARE_FUNC, GL.LEQUAL); - } - else { - SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL2.TEXTURE_COMPARE_MODE, GL.NONE); - } - } - - public function setCubeMapCompareMode(texunit: kha.graphics4.TextureUnit, enabled: Bool) { - if (enabled) { - SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL2.TEXTURE_COMPARE_MODE, GL2.COMPARE_REF_TO_TEXTURE); - SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL2.TEXTURE_COMPARE_FUNC, GL.LEQUAL); - } - else { - SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL2.TEXTURE_COMPARE_MODE, GL.NONE); - } - } - - public function setCubeMap(stage: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void { - if (cubeMap == null) { - SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast stage : TextureUnit).value); - SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, null); - } - else { - cubeMap.set((cast stage : TextureUnit).value); - } - } - - public function setCubeMapDepth(stage: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void { - cubeMap.setDepth((cast stage : TextureUnit).value); - } - - public function maxBoundTextures(): Int { - return SystemImpl.gl.getParameter(GL.MAX_TEXTURE_IMAGE_UNITS); - } - - public function setCullMode(mode: CullMode): Void { - switch (mode) { - case None: - SystemImpl.gl.disable(GL.CULL_FACE); - case Clockwise: - SystemImpl.gl.enable(GL.CULL_FACE); - SystemImpl.gl.cullFace(GL.BACK); - case CounterClockwise: - SystemImpl.gl.enable(GL.CULL_FACE); - SystemImpl.gl.cullFace(GL.FRONT); - } - } - - public function setPipeline(pipe: PipelineState): Void { - setCullMode(pipe.cullMode); - setDepthMode(pipe.depthWrite, pipe.depthMode); - if (pipe.stencilFrontMode == Always && pipe.stencilBackMode == Always && pipe.stencilFrontBothPass == Keep && pipe.stencilBackBothPass == Keep - && pipe.stencilFrontDepthFail == Keep && pipe.stencilBackDepthFail == Keep && pipe.stencilFrontFail == Keep && pipe.stencilBackFail == Keep) { - SystemImpl.gl.disable(GL.STENCIL_TEST); - } - else { - SystemImpl.gl.enable(GL.STENCIL_TEST); - setStencilParameters(true, pipe.stencilFrontMode, pipe.stencilFrontBothPass, pipe.stencilFrontDepthFail, pipe.stencilFrontFail, - pipe.stencilReferenceValue, pipe.stencilReadMask, pipe.stencilWriteMask); - setStencilParameters(false, pipe.stencilBackMode, pipe.stencilBackBothPass, pipe.stencilBackDepthFail, pipe.stencilBackFail, - pipe.stencilReferenceValue, pipe.stencilReadMask, pipe.stencilWriteMask); - } - setBlendingMode(pipe.blendSource, pipe.blendDestination, pipe.blendOperation, pipe.alphaBlendSource, pipe.alphaBlendDestination, - pipe.alphaBlendOperation); - currentPipeline = pipe; - pipe.set(); - colorMaskRed = pipe.colorWriteMaskRed; - colorMaskGreen = pipe.colorWriteMaskGreen; - colorMaskBlue = pipe.colorWriteMaskBlue; - colorMaskAlpha = pipe.colorWriteMaskAlpha; - } - - public function setStencilReferenceValue(value: Int): Void { - SystemImpl.gl.stencilFuncSeparate(GL.FRONT, convertCompareMode(currentPipeline.stencilFrontMode), value, currentPipeline.stencilReadMask); - SystemImpl.gl.stencilFuncSeparate(GL.BACK, convertCompareMode(currentPipeline.stencilBackMode), value, currentPipeline.stencilReadMask); - } - - public function setBool(location: kha.graphics4.ConstantLocation, value: Bool): Void { - SystemImpl.gl.uniform1i((cast location : ConstantLocation).value, value ? 1 : 0); - } - - public function setInt(location: kha.graphics4.ConstantLocation, value: Int): Void { - SystemImpl.gl.uniform1i((cast location : ConstantLocation).value, value); - } - - public function setInt2(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int): Void { - SystemImpl.gl.uniform2i((cast location : ConstantLocation).value, value1, value2); - } - - public function setInt3(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int): Void { - SystemImpl.gl.uniform3i((cast location : ConstantLocation).value, value1, value2, value3); - } - - public function setInt4(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int, value4: Int): Void { - SystemImpl.gl.uniform4i((cast location : ConstantLocation).value, value1, value2, value3, value4); - } - - public function setInts(location: kha.graphics4.ConstantLocation, values: Int32Array): Void { - var webglLocation = (cast location : ConstantLocation); - var rawValues = new js.lib.Int32Array(values.buffer, values.byteOffset, values.length); - switch (webglLocation.type) { - case GL.INT_VEC2: - SystemImpl.gl.uniform2iv(webglLocation.value, rawValues); - case GL.INT_VEC3: - SystemImpl.gl.uniform3iv(webglLocation.value, rawValues); - case GL.INT_VEC4: - SystemImpl.gl.uniform4iv(webglLocation.value, rawValues); - default: - SystemImpl.gl.uniform1iv(webglLocation.value, rawValues); - } - } - - public function setFloat(location: kha.graphics4.ConstantLocation, value: FastFloat): Void { - SystemImpl.gl.uniform1f((cast location : ConstantLocation).value, value); - } - - public function setFloat2(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat): Void { - SystemImpl.gl.uniform2f((cast location : ConstantLocation).value, value1, value2); - } - - public function setFloat3(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void { - SystemImpl.gl.uniform3f((cast location : ConstantLocation).value, value1, value2, value3); - } - - public function setFloat4(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void { - SystemImpl.gl.uniform4f((cast location : ConstantLocation).value, value1, value2, value3, value4); - } - - public function setFloats(location: kha.graphics4.ConstantLocation, values: Float32Array): Void { - var webglLocation = (cast location : ConstantLocation); - var rawValues = new js.lib.Float32Array(values.buffer, values.byteOffset, values.length); - switch (webglLocation.type) { - case GL.FLOAT_VEC2: - SystemImpl.gl.uniform2fv(webglLocation.value, rawValues); - case GL.FLOAT_VEC3: - SystemImpl.gl.uniform3fv(webglLocation.value, rawValues); - case GL.FLOAT_VEC4: - SystemImpl.gl.uniform4fv(webglLocation.value, rawValues); - case GL.FLOAT_MAT4: - SystemImpl.gl.uniformMatrix4fv(webglLocation.value, false, rawValues); - default: - SystemImpl.gl.uniform1fv(webglLocation.value, rawValues); - } - } - - public function setVector2(location: kha.graphics4.ConstantLocation, value: FastVector2): Void { - SystemImpl.gl.uniform2f((cast location : ConstantLocation).value, value.x, value.y); - } - - public function setVector3(location: kha.graphics4.ConstantLocation, value: FastVector3): Void { - SystemImpl.gl.uniform3f((cast location : ConstantLocation).value, value.x, value.y, value.z); - } - - public function setVector4(location: kha.graphics4.ConstantLocation, value: FastVector4): Void { - SystemImpl.gl.uniform4f((cast location : ConstantLocation).value, value.x, value.y, value.z, value.w); - } - - static var matrixCache = new js.lib.Float32Array(16); - public inline function setMatrix(location: kha.graphics4.ConstantLocation, matrix: FastMatrix4): Void { - matrixCache[0] = matrix._00; - matrixCache[1] = matrix._01; - matrixCache[2] = matrix._02; - matrixCache[3] = matrix._03; - matrixCache[4] = matrix._10; - matrixCache[5] = matrix._11; - matrixCache[6] = matrix._12; - matrixCache[7] = matrix._13; - matrixCache[8] = matrix._20; - matrixCache[9] = matrix._21; - matrixCache[10] = matrix._22; - matrixCache[11] = matrix._23; - matrixCache[12] = matrix._30; - matrixCache[13] = matrix._31; - matrixCache[14] = matrix._32; - matrixCache[15] = matrix._33; - SystemImpl.gl.uniformMatrix4fv((cast location : ConstantLocation).value, false, matrixCache); - } - - static var matrix3Cache = new js.lib.Float32Array(9); - public inline function setMatrix3(location: kha.graphics4.ConstantLocation, matrix: FastMatrix3): Void { - matrix3Cache[0] = matrix._00; - matrix3Cache[1] = matrix._01; - matrix3Cache[2] = matrix._02; - matrix3Cache[3] = matrix._10; - matrix3Cache[4] = matrix._11; - matrix3Cache[5] = matrix._12; - matrix3Cache[6] = matrix._20; - matrix3Cache[7] = matrix._21; - matrix3Cache[8] = matrix._22; - SystemImpl.gl.uniformMatrix3fv((cast location : ConstantLocation).value, false, matrix3Cache); - } - - public function drawIndexedVertices(start: Int = 0, count: Int = -1): Void { - var type = SystemImpl.elementIndexUint == null ? GL.UNSIGNED_SHORT : GL.UNSIGNED_INT; - var size = type == GL.UNSIGNED_SHORT ? 2 : 4; - SystemImpl.gl.drawElements(GL.TRIANGLES, count == -1 ? indicesCount : count, type, start * size); - } - - function convertStencilAction(action: StencilAction) { - switch (action) { - case StencilAction.Decrement: - return GL.DECR; - case StencilAction.DecrementWrap: - return GL.DECR_WRAP; - case StencilAction.Increment: - return GL.INCR; - case StencilAction.IncrementWrap: - return GL.INCR_WRAP; - case StencilAction.Invert: - return GL.INVERT; - case StencilAction.Keep: - return GL.KEEP; - case StencilAction.Replace: - return GL.REPLACE; - case StencilAction.Zero: - return GL.ZERO; - } - } - - function convertCompareMode(compareMode: CompareMode) { - switch (compareMode) { - case Always: - return GL.ALWAYS; - case Equal: - return GL.EQUAL; - case Greater: - return GL.GREATER; - case GreaterEqual: - return GL.GEQUAL; - case Less: - return GL.LESS; - case LessEqual: - return GL.LEQUAL; - case Never: - return GL.NEVER; - case NotEqual: - return GL.NOTEQUAL; - } - } - - public function setStencilParameters(front: Bool, compareMode: CompareMode, bothPass: StencilAction, depthFail: StencilAction, stencilFail: StencilAction, - referenceValue: StencilValue, readMask: Int = 0xff, writeMask: Int = 0xff): Void { - var stencilFunc = convertCompareMode(compareMode); - SystemImpl.gl.stencilMaskSeparate(front ? GL.FRONT : GL.BACK, writeMask); - SystemImpl.gl.stencilOpSeparate(front ? GL.FRONT : GL.BACK, convertStencilAction(stencilFail), convertStencilAction(depthFail), - convertStencilAction(bothPass)); - switch (referenceValue) { - case Static(value): - SystemImpl.gl.stencilFuncSeparate(front ? GL.FRONT : GL.BACK, stencilFunc, value, readMask); - case Dynamic: - SystemImpl.gl.stencilFuncSeparate(front ? GL.FRONT : GL.BACK, stencilFunc, 0, readMask); - } - } - - public function drawIndexedVerticesInstanced(instanceCount: Int, start: Int = 0, count: Int = -1) { - if (instancedRenderingAvailable()) { - var type = SystemImpl.elementIndexUint == null ? GL.UNSIGNED_SHORT : GL.UNSIGNED_INT; - var typeSize = SystemImpl.elementIndexUint == null ? 2 : 4; - if (SystemImpl.gl2) { - untyped SystemImpl.gl.drawElementsInstanced(GL.TRIANGLES, count == -1 ? indicesCount : count, type, start * typeSize, instanceCount); - } - else { - instancedExtension.drawElementsInstancedANGLE(GL.TRIANGLES, count == -1 ? indicesCount : count, type, start * typeSize, instanceCount); - } - } - } - - public function instancedRenderingAvailable(): Bool { - return instancedExtension; - } -} +package kha.js.graphics4; + +import js.html.webgl.GL2; +import kha.graphics4.StencilValue; +import kha.arrays.Float32Array; +import kha.arrays.Int32Array; +import js.html.webgl.GL; +import kha.graphics4.BlendingFactor; +import kha.graphics4.BlendingOperation; +import kha.graphics4.CompareMode; +import kha.graphics4.CubeMap; +import kha.graphics4.CullMode; +import kha.graphics4.IndexBuffer; +import kha.graphics4.MipMapFilter; +import kha.graphics4.PipelineState; +import kha.graphics4.StencilAction; +import kha.graphics4.TextureAddressing; +import kha.graphics4.TextureFilter; +import kha.graphics4.Usage; +import kha.graphics4.VertexBuffer; +import kha.graphics4.VertexStructure; +import kha.Image; +import kha.math.FastMatrix3; +import kha.math.FastMatrix4; +import kha.math.FastVector2; +import kha.math.FastVector3; +import kha.math.FastVector4; +import kha.WebGLImage; + +class Graphics implements kha.graphics4.Graphics { + var currentPipeline: PipelineState = null; + var depthTest: Bool = false; + var depthMask: Bool = false; + var colorMaskRed: Bool = true; + var colorMaskGreen: Bool = true; + var colorMaskBlue: Bool = true; + var colorMaskAlpha: Bool = true; + var indicesCount: Int; + var renderTarget: Canvas; + var renderTargetFrameBuffer: Dynamic; + var renderTargetMSAA: Dynamic; + var renderTargetTexture: Dynamic; + var isCubeMap: Bool = false; + var isDepthAttachment: Bool = false; + var instancedExtension: Dynamic; + var blendMinMaxExtension: Dynamic; + + static var current: Graphics = null; + static var useVertexAttributes: Int = 0; + public static var vrFramebufferBound: Bool = false; + + public function new(renderTarget: Canvas = null) { + this.renderTarget = renderTarget; + init(); + if (SystemImpl.gl2) { + instancedExtension = true; + } + else { + instancedExtension = SystemImpl.gl.getExtension("ANGLE_instanced_arrays"); + blendMinMaxExtension = SystemImpl.gl.getExtension("EXT_blend_minmax"); + } + } + + function init() { + if (renderTarget == null) + return; + isCubeMap = Std.isOfType(renderTarget, CubeMap); + if (isCubeMap) { + var cubeMap: CubeMap = cast(renderTarget, CubeMap); + renderTargetFrameBuffer = cubeMap.frameBuffer; + renderTargetTexture = cubeMap.texture; + isDepthAttachment = cubeMap.isDepthAttachment; + } + else { + var image: WebGLImage = cast(renderTarget, WebGLImage); + renderTargetFrameBuffer = image.frameBuffer; + renderTargetMSAA = image.MSAAFrameBuffer; + renderTargetTexture = image.texture; + } + } + + public function begin(additionalRenderTargets: Array = null): Void { + if (current == null) { + current = this; + } + else { + throw "End before you begin"; + } + + SystemImpl.gl.enable(GL.BLEND); + SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); + if (renderTarget == null) { + if (!vrFramebufferBound) { + SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); + SystemImpl.gl.viewport(0, 0, System.windowWidth(), System.windowHeight()); + } + } + else { + SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, renderTargetFrameBuffer); + // if (isCubeMap) SystemImpl.gl.framebufferTexture(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_CUBE_MAP, renderTargetTexture, 0); // Layered + SystemImpl.gl.viewport(0, 0, renderTarget.width, renderTarget.height); + if (additionalRenderTargets != null) { + SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL, GL.TEXTURE_2D, renderTargetTexture, 0); + for (i in 0...additionalRenderTargets.length) { + SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL + i + 1, GL.TEXTURE_2D, + cast(additionalRenderTargets[i], WebGLImage).texture, 0); + } + var attachments = [SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL]; + for (i in 0...additionalRenderTargets.length) { + attachments.push(SystemImpl.drawBuffers.COLOR_ATTACHMENT0_WEBGL + i + 1); + } + SystemImpl.gl2 ? untyped SystemImpl.gl.drawBuffers(attachments) : SystemImpl.drawBuffers.drawBuffersWEBGL(attachments); + } + } + } + + public function beginFace(face: Int): Void { + if (current == null) { + current = this; + } + else { + throw "End before you begin"; + } + + SystemImpl.gl.enable(GL.BLEND); + SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); + SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, renderTargetFrameBuffer); + SystemImpl.gl.framebufferTexture2D(GL.FRAMEBUFFER, isDepthAttachment ? GL.DEPTH_ATTACHMENT : GL.COLOR_ATTACHMENT0, + GL.TEXTURE_CUBE_MAP_POSITIVE_X + face, renderTargetTexture, 0); + SystemImpl.gl.viewport(0, 0, renderTarget.width, renderTarget.height); + } + + public function beginEye(eye: Int): Void { + if (current == null) { + current = this; + } + else { + throw "End before you begin"; + } + + SystemImpl.gl.enable(GL.BLEND); + SystemImpl.gl.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); + SystemImpl.gl.bindFramebuffer(GL.FRAMEBUFFER, null); + if (eye == 0) { + SystemImpl.gl.viewport(0, 0, Std.int(System.windowWidth() * 0.5), System.windowHeight()); + } + else { + SystemImpl.gl.viewport(Std.int(System.windowWidth() * 0.5), 0, Std.int(System.windowWidth() * 0.5), System.windowHeight()); + } + } + + public function end(): Void { + if (current == this) { + current = null; + } + else { + throw "Begin before you end"; + } + + if (renderTargetMSAA != null) { + untyped SystemImpl.gl.bindFramebuffer(SystemImpl.gl.READ_FRAMEBUFFER, renderTargetFrameBuffer); + untyped SystemImpl.gl.bindFramebuffer(SystemImpl.gl.DRAW_FRAMEBUFFER, renderTargetMSAA); + untyped SystemImpl.gl.blitFramebuffer(0, 0, renderTarget.width, renderTarget.height, 0, 0, renderTarget.width, renderTarget.height, + GL.COLOR_BUFFER_BIT, GL.NEAREST); + } + #if (debug || kha_debug_html5) + var error = SystemImpl.gl.getError(); + switch (error) { + case GL.NO_ERROR: + + case GL.INVALID_ENUM: + trace("WebGL error: Invalid enum"); + case GL.INVALID_VALUE: + trace("WebGL error: Invalid value"); + case GL.INVALID_OPERATION: + trace("WebGL error: Invalid operation"); + case GL.INVALID_FRAMEBUFFER_OPERATION: + trace("WebGL error: Invalid framebuffer operation"); + case GL.OUT_OF_MEMORY: + trace("WebGL error: Out of memory"); + case GL.CONTEXT_LOST_WEBGL: + trace("WebGL error: Context lost"); + default: + trace("Unknown WebGL error"); + } + #end + } + + public function flush(): Void {} + + public function vsynced(): Bool { + return true; + } + + public function refreshRate(): Int { + return 60; + } + + public function clear(?color: Color, ?depth: Float, ?stencil: Int): Void { + var clearMask: Int = 0; + if (color != null) { + clearMask |= GL.COLOR_BUFFER_BIT; + SystemImpl.gl.colorMask(true, true, true, true); + SystemImpl.gl.clearColor(color.R, color.G, color.B, color.A); + } + if (depth != null) { + clearMask |= GL.DEPTH_BUFFER_BIT; + SystemImpl.gl.enable(GL.DEPTH_TEST); + SystemImpl.gl.depthMask(true); + SystemImpl.gl.clearDepth(depth); + } + if (stencil != null) { + clearMask |= GL.STENCIL_BUFFER_BIT; + SystemImpl.gl.enable(GL.STENCIL_TEST); + SystemImpl.gl.stencilMask(0xff); + SystemImpl.gl.clearStencil(stencil); + } + SystemImpl.gl.clear(clearMask); + SystemImpl.gl.colorMask(colorMaskRed, colorMaskGreen, colorMaskBlue, colorMaskAlpha); + if (depthTest) { + SystemImpl.gl.enable(GL.DEPTH_TEST); + } + else { + SystemImpl.gl.disable(GL.DEPTH_TEST); + } + SystemImpl.gl.depthMask(depthMask); + } + + public function viewport(x: Int, y: Int, width: Int, height: Int): Void { + if (renderTarget == null) { + SystemImpl.gl.viewport(x, System.windowHeight(0) - y - height, width, height); + } + else { + SystemImpl.gl.viewport(x, y, width, height); + } + } + + public function scissor(x: Int, y: Int, width: Int, height: Int): Void { + SystemImpl.gl.enable(GL.SCISSOR_TEST); + if (renderTarget == null) { + SystemImpl.gl.scissor(x, System.windowHeight(0) - y - height, width, height); + } + else { + SystemImpl.gl.scissor(x, y, width, height); + } + } + + public function disableScissor(): Void { + SystemImpl.gl.disable(GL.SCISSOR_TEST); + } + + public function setDepthMode(write: Bool, mode: CompareMode): Void { + switch (mode) { + case Always: + write ? SystemImpl.gl.enable(GL.DEPTH_TEST) : SystemImpl.gl.disable(GL.DEPTH_TEST); + depthTest = write; + SystemImpl.gl.depthFunc(GL.ALWAYS); + case Never: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.NEVER); + case Equal: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.EQUAL); + case NotEqual: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.NOTEQUAL); + case Less: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.LESS); + case LessEqual: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.LEQUAL); + case Greater: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.GREATER); + case GreaterEqual: + SystemImpl.gl.enable(GL.DEPTH_TEST); + depthTest = true; + SystemImpl.gl.depthFunc(GL.GEQUAL); + } + SystemImpl.gl.depthMask(write); + depthMask = write; + } + + static function getBlendFunc(factor: BlendingFactor): Int { + switch (factor) { + case BlendZero, Undefined: + return GL.ZERO; + case BlendOne: + return GL.ONE; + case SourceAlpha: + return GL.SRC_ALPHA; + case DestinationAlpha: + return GL.DST_ALPHA; + case InverseSourceAlpha: + return GL.ONE_MINUS_SRC_ALPHA; + case InverseDestinationAlpha: + return GL.ONE_MINUS_DST_ALPHA; + case SourceColor: + return GL.SRC_COLOR; + case DestinationColor: + return GL.DST_COLOR; + case InverseSourceColor: + return GL.ONE_MINUS_SRC_COLOR; + case InverseDestinationColor: + return GL.ONE_MINUS_DST_COLOR; + } + } + + static function getBlendOp(op: BlendingOperation): Int { + switch (op) { + case Add: + return GL.FUNC_ADD; + case Subtract: + return GL.FUNC_SUBTRACT; + case ReverseSubtract: + return GL.FUNC_REVERSE_SUBTRACT; + case Min: + return 0x8007; + case Max: + return 0x8008; + } + } + + public function setBlendingMode(source: BlendingFactor, destination: BlendingFactor, operation: BlendingOperation, alphaSource: BlendingFactor, + alphaDestination: BlendingFactor, alphaOperation: BlendingOperation): Void { + if (source == BlendOne && destination == BlendZero) { + SystemImpl.gl.disable(GL.BLEND); + } + else { + SystemImpl.gl.enable(GL.BLEND); + SystemImpl.gl.blendFuncSeparate(getBlendFunc(source), getBlendFunc(destination), getBlendFunc(alphaSource), getBlendFunc(alphaDestination)); + SystemImpl.gl.blendEquationSeparate(getBlendOp(operation), getBlendOp(alphaOperation)); + } + } + + public function createVertexBuffer(vertexCount: Int, structure: VertexStructure, usage: Usage, canRead: Bool = false): kha.graphics4.VertexBuffer { + return new VertexBuffer(vertexCount, structure, usage); + } + + public function setVertexBuffer(vertexBuffer: kha.graphics4.VertexBuffer): Void { + for (i in 0...useVertexAttributes) { + SystemImpl.gl.disableVertexAttribArray(i); + } + useVertexAttributes = vertexBuffer.set(0); + } + + public function setVertexBuffers(vertexBuffers: Array): Void { + for (i in 0...useVertexAttributes) { + SystemImpl.gl.disableVertexAttribArray(i); + } + var offset: Int = 0; + for (vertexBuffer in vertexBuffers) { + offset += vertexBuffer.set(offset); + } + useVertexAttributes = offset; + } + + public function createIndexBuffer(indexCount: Int, usage: Usage, canRead: Bool = false): kha.graphics4.IndexBuffer { + return new IndexBuffer(indexCount, usage); + } + + public function setIndexBuffer(indexBuffer: kha.graphics4.IndexBuffer): Void { + indicesCount = indexBuffer.count(); + indexBuffer.set(); + } + + // public function maxTextureSize(): Int { + // return Sys.gl == null ? 8192 : Sys.gl.getParameter(Sys.gl.MAX_TEXTURE_SIZE); + // } + // public function supportsNonPow2Textures(): Bool { + // return false; + // } + + public function setTexture(stage: kha.graphics4.TextureUnit, texture: kha.Image): Void { + if (texture == null) { + SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast stage : TextureUnit).value); + SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null); + } + else { + cast(texture, WebGLImage).set((cast stage : TextureUnit).value); + } + } + + public function setTextureDepth(stage: kha.graphics4.TextureUnit, texture: kha.Image): Void { + cast(texture, WebGLImage).setDepth((cast stage : TextureUnit).value); + } + + public function setTextureArray(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void { + // not implemented yet. + } + + public function setVideoTexture(unit: kha.graphics4.TextureUnit, texture: kha.Video): Void { + if (texture == null) { + SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast unit : TextureUnit).value); + SystemImpl.gl.bindTexture(GL.TEXTURE_2D, null); + } + else { + cast((cast texture : kha.js.Video).texture, WebGLImage).set((cast unit : TextureUnit).value); + } + } + + public function setImageTexture(unit: kha.graphics4.TextureUnit, texture: kha.Image): Void {} + + public function setTextureParameters(texunit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing, + minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void { + SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast texunit : TextureUnit).value); + + switch (uAddressing) { + case Clamp: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); + case Repeat: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.REPEAT); + case Mirror: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.MIRRORED_REPEAT); + } + + switch (vAddressing) { + case Clamp: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); + case Repeat: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.REPEAT); + case Mirror: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.MIRRORED_REPEAT); + } + + switch (minificationFilter) { + case PointFilter: + switch (mipmapFilter) { + case NoMipFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST); + case PointMipFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST_MIPMAP_NEAREST); + case LinearMipFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST_MIPMAP_LINEAR); + } + case LinearFilter, AnisotropicFilter: + switch (mipmapFilter) { + case NoMipFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); + case PointMipFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_NEAREST); + case LinearMipFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_LINEAR); + } + if (minificationFilter == AnisotropicFilter) { + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, SystemImpl.anisotropicFilter.TEXTURE_MAX_ANISOTROPY_EXT, 4); + } + } + + switch (magnificationFilter) { + case PointFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST); + case LinearFilter, AnisotropicFilter: + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); + } + } + + public function setTexture3DParameters(texunit: kha.graphics4.TextureUnit, uAddressing: TextureAddressing, vAddressing: TextureAddressing, + wAddressing: TextureAddressing, minificationFilter: TextureFilter, magnificationFilter: TextureFilter, mipmapFilter: MipMapFilter): Void {} + + public function setTextureCompareMode(texunit: kha.graphics4.TextureUnit, enabled: Bool) { + if (enabled) { + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL2.TEXTURE_COMPARE_MODE, GL2.COMPARE_REF_TO_TEXTURE); + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL2.TEXTURE_COMPARE_FUNC, GL.LEQUAL); + } + else { + SystemImpl.gl.texParameteri(GL.TEXTURE_2D, GL2.TEXTURE_COMPARE_MODE, GL.NONE); + } + } + + public function setCubeMapCompareMode(texunit: kha.graphics4.TextureUnit, enabled: Bool) { + if (enabled) { + SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL2.TEXTURE_COMPARE_MODE, GL2.COMPARE_REF_TO_TEXTURE); + SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL2.TEXTURE_COMPARE_FUNC, GL.LEQUAL); + } + else { + SystemImpl.gl.texParameteri(GL.TEXTURE_CUBE_MAP, GL2.TEXTURE_COMPARE_MODE, GL.NONE); + } + } + + public function setCubeMap(stage: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void { + if (cubeMap == null) { + SystemImpl.gl.activeTexture(GL.TEXTURE0 + (cast stage : TextureUnit).value); + SystemImpl.gl.bindTexture(GL.TEXTURE_CUBE_MAP, null); + } + else { + cubeMap.set((cast stage : TextureUnit).value); + } + } + + public function setCubeMapDepth(stage: kha.graphics4.TextureUnit, cubeMap: kha.graphics4.CubeMap): Void { + cubeMap.setDepth((cast stage : TextureUnit).value); + } + + public function maxBoundTextures(): Int { + return SystemImpl.gl.getParameter(GL.MAX_TEXTURE_IMAGE_UNITS); + } + + public function setCullMode(mode: CullMode): Void { + switch (mode) { + case None: + SystemImpl.gl.disable(GL.CULL_FACE); + case Clockwise: + SystemImpl.gl.enable(GL.CULL_FACE); + SystemImpl.gl.cullFace(GL.BACK); + case CounterClockwise: + SystemImpl.gl.enable(GL.CULL_FACE); + SystemImpl.gl.cullFace(GL.FRONT); + } + } + + public function setPipeline(pipe: PipelineState): Void { + setCullMode(pipe.cullMode); + setDepthMode(pipe.depthWrite, pipe.depthMode); + if (pipe.stencilFrontMode == Always && pipe.stencilBackMode == Always && pipe.stencilFrontBothPass == Keep && pipe.stencilBackBothPass == Keep + && pipe.stencilFrontDepthFail == Keep && pipe.stencilBackDepthFail == Keep && pipe.stencilFrontFail == Keep && pipe.stencilBackFail == Keep) { + SystemImpl.gl.disable(GL.STENCIL_TEST); + } + else { + SystemImpl.gl.enable(GL.STENCIL_TEST); + setStencilParameters(true, pipe.stencilFrontMode, pipe.stencilFrontBothPass, pipe.stencilFrontDepthFail, pipe.stencilFrontFail, + pipe.stencilReferenceValue, pipe.stencilReadMask, pipe.stencilWriteMask); + setStencilParameters(false, pipe.stencilBackMode, pipe.stencilBackBothPass, pipe.stencilBackDepthFail, pipe.stencilBackFail, + pipe.stencilReferenceValue, pipe.stencilReadMask, pipe.stencilWriteMask); + } + setBlendingMode(pipe.blendSource, pipe.blendDestination, pipe.blendOperation, pipe.alphaBlendSource, pipe.alphaBlendDestination, + pipe.alphaBlendOperation); + currentPipeline = pipe; + pipe.set(); + colorMaskRed = pipe.colorWriteMaskRed; + colorMaskGreen = pipe.colorWriteMaskGreen; + colorMaskBlue = pipe.colorWriteMaskBlue; + colorMaskAlpha = pipe.colorWriteMaskAlpha; + } + + public function setStencilReferenceValue(value: Int): Void { + SystemImpl.gl.stencilFuncSeparate(GL.FRONT, convertCompareMode(currentPipeline.stencilFrontMode), value, currentPipeline.stencilReadMask); + SystemImpl.gl.stencilFuncSeparate(GL.BACK, convertCompareMode(currentPipeline.stencilBackMode), value, currentPipeline.stencilReadMask); + } + + public function setBool(location: kha.graphics4.ConstantLocation, value: Bool): Void { + SystemImpl.gl.uniform1i((cast location : ConstantLocation).value, value ? 1 : 0); + } + + public function setInt(location: kha.graphics4.ConstantLocation, value: Int): Void { + SystemImpl.gl.uniform1i((cast location : ConstantLocation).value, value); + } + + public function setInt2(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int): Void { + SystemImpl.gl.uniform2i((cast location : ConstantLocation).value, value1, value2); + } + + public function setInt3(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int): Void { + SystemImpl.gl.uniform3i((cast location : ConstantLocation).value, value1, value2, value3); + } + + public function setInt4(location: kha.graphics4.ConstantLocation, value1: Int, value2: Int, value3: Int, value4: Int): Void { + SystemImpl.gl.uniform4i((cast location : ConstantLocation).value, value1, value2, value3, value4); + } + + public function setInts(location: kha.graphics4.ConstantLocation, values: Int32Array): Void { + var webglLocation = (cast location : ConstantLocation); + var rawValues = new js.lib.Int32Array(values.buffer, values.byteOffset, values.length); + switch (webglLocation.type) { + case GL.INT_VEC2: + SystemImpl.gl.uniform2iv(webglLocation.value, rawValues); + case GL.INT_VEC3: + SystemImpl.gl.uniform3iv(webglLocation.value, rawValues); + case GL.INT_VEC4: + SystemImpl.gl.uniform4iv(webglLocation.value, rawValues); + default: + SystemImpl.gl.uniform1iv(webglLocation.value, rawValues); + } + } + + public function setFloat(location: kha.graphics4.ConstantLocation, value: FastFloat): Void { + SystemImpl.gl.uniform1f((cast location : ConstantLocation).value, value); + } + + public function setFloat2(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat): Void { + SystemImpl.gl.uniform2f((cast location : ConstantLocation).value, value1, value2); + } + + public function setFloat3(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat): Void { + SystemImpl.gl.uniform3f((cast location : ConstantLocation).value, value1, value2, value3); + } + + public function setFloat4(location: kha.graphics4.ConstantLocation, value1: FastFloat, value2: FastFloat, value3: FastFloat, value4: FastFloat): Void { + SystemImpl.gl.uniform4f((cast location : ConstantLocation).value, value1, value2, value3, value4); + } + + public function setFloats(location: kha.graphics4.ConstantLocation, values: Float32Array): Void { + var webglLocation = (cast location : ConstantLocation); + var rawValues = new js.lib.Float32Array(values.buffer, values.byteOffset, values.length); + switch (webglLocation.type) { + case GL.FLOAT_VEC2: + SystemImpl.gl.uniform2fv(webglLocation.value, rawValues); + case GL.FLOAT_VEC3: + SystemImpl.gl.uniform3fv(webglLocation.value, rawValues); + case GL.FLOAT_VEC4: + SystemImpl.gl.uniform4fv(webglLocation.value, rawValues); + case GL.FLOAT_MAT4: + SystemImpl.gl.uniformMatrix4fv(webglLocation.value, false, rawValues); + default: + SystemImpl.gl.uniform1fv(webglLocation.value, rawValues); + } + } + + public function setVector2(location: kha.graphics4.ConstantLocation, value: FastVector2): Void { + SystemImpl.gl.uniform2f((cast location : ConstantLocation).value, value.x, value.y); + } + + public function setVector3(location: kha.graphics4.ConstantLocation, value: FastVector3): Void { + SystemImpl.gl.uniform3f((cast location : ConstantLocation).value, value.x, value.y, value.z); + } + + public function setVector4(location: kha.graphics4.ConstantLocation, value: FastVector4): Void { + SystemImpl.gl.uniform4f((cast location : ConstantLocation).value, value.x, value.y, value.z, value.w); + } + + static var matrixCache = new js.lib.Float32Array(16); + public inline function setMatrix(location: kha.graphics4.ConstantLocation, matrix: FastMatrix4): Void { + matrixCache[0] = matrix._00; + matrixCache[1] = matrix._01; + matrixCache[2] = matrix._02; + matrixCache[3] = matrix._03; + matrixCache[4] = matrix._10; + matrixCache[5] = matrix._11; + matrixCache[6] = matrix._12; + matrixCache[7] = matrix._13; + matrixCache[8] = matrix._20; + matrixCache[9] = matrix._21; + matrixCache[10] = matrix._22; + matrixCache[11] = matrix._23; + matrixCache[12] = matrix._30; + matrixCache[13] = matrix._31; + matrixCache[14] = matrix._32; + matrixCache[15] = matrix._33; + SystemImpl.gl.uniformMatrix4fv((cast location : ConstantLocation).value, false, matrixCache); + } + + static var matrix3Cache = new js.lib.Float32Array(9); + public inline function setMatrix3(location: kha.graphics4.ConstantLocation, matrix: FastMatrix3): Void { + matrix3Cache[0] = matrix._00; + matrix3Cache[1] = matrix._01; + matrix3Cache[2] = matrix._02; + matrix3Cache[3] = matrix._10; + matrix3Cache[4] = matrix._11; + matrix3Cache[5] = matrix._12; + matrix3Cache[6] = matrix._20; + matrix3Cache[7] = matrix._21; + matrix3Cache[8] = matrix._22; + SystemImpl.gl.uniformMatrix3fv((cast location : ConstantLocation).value, false, matrix3Cache); + } + + public function drawIndexedVertices(start: Int = 0, count: Int = -1): Void { + var type = SystemImpl.elementIndexUint == null ? GL.UNSIGNED_SHORT : GL.UNSIGNED_INT; + var size = type == GL.UNSIGNED_SHORT ? 2 : 4; + SystemImpl.gl.drawElements(GL.TRIANGLES, count == -1 ? indicesCount : count, type, start * size); + } + + function convertStencilAction(action: StencilAction) { + switch (action) { + case StencilAction.Decrement: + return GL.DECR; + case StencilAction.DecrementWrap: + return GL.DECR_WRAP; + case StencilAction.Increment: + return GL.INCR; + case StencilAction.IncrementWrap: + return GL.INCR_WRAP; + case StencilAction.Invert: + return GL.INVERT; + case StencilAction.Keep: + return GL.KEEP; + case StencilAction.Replace: + return GL.REPLACE; + case StencilAction.Zero: + return GL.ZERO; + } + } + + function convertCompareMode(compareMode: CompareMode) { + switch (compareMode) { + case Always: + return GL.ALWAYS; + case Equal: + return GL.EQUAL; + case Greater: + return GL.GREATER; + case GreaterEqual: + return GL.GEQUAL; + case Less: + return GL.LESS; + case LessEqual: + return GL.LEQUAL; + case Never: + return GL.NEVER; + case NotEqual: + return GL.NOTEQUAL; + } + } + + public function setStencilParameters(front: Bool, compareMode: CompareMode, bothPass: StencilAction, depthFail: StencilAction, stencilFail: StencilAction, + referenceValue: StencilValue, readMask: Int = 0xff, writeMask: Int = 0xff): Void { + var stencilFunc = convertCompareMode(compareMode); + SystemImpl.gl.stencilMaskSeparate(front ? GL.FRONT : GL.BACK, writeMask); + SystemImpl.gl.stencilOpSeparate(front ? GL.FRONT : GL.BACK, convertStencilAction(stencilFail), convertStencilAction(depthFail), + convertStencilAction(bothPass)); + switch (referenceValue) { + case Static(value): + SystemImpl.gl.stencilFuncSeparate(front ? GL.FRONT : GL.BACK, stencilFunc, value, readMask); + case Dynamic: + SystemImpl.gl.stencilFuncSeparate(front ? GL.FRONT : GL.BACK, stencilFunc, 0, readMask); + } + } + + public function drawIndexedVerticesInstanced(instanceCount: Int, start: Int = 0, count: Int = -1) { + if (instancedRenderingAvailable()) { + var type = SystemImpl.elementIndexUint == null ? GL.UNSIGNED_SHORT : GL.UNSIGNED_INT; + var typeSize = SystemImpl.elementIndexUint == null ? 2 : 4; + if (SystemImpl.gl2) { + untyped SystemImpl.gl.drawElementsInstanced(GL.TRIANGLES, count == -1 ? indicesCount : count, type, start * typeSize, instanceCount); + } + else { + instancedExtension.drawElementsInstancedANGLE(GL.TRIANGLES, count == -1 ? indicesCount : count, type, start * typeSize, instanceCount); + } + } + } + + public function instancedRenderingAvailable(): Bool { + return instancedExtension; + } +} diff --git a/Kha/Backends/HTML5/kha/js/vr/VrInterface.hx b/Kha/Backends/HTML5/kha/js/vr/VrInterface.hx index 0d9f780..2f45d38 100755 --- a/Kha/Backends/HTML5/kha/js/vr/VrInterface.hx +++ b/Kha/Backends/HTML5/kha/js/vr/VrInterface.hx @@ -1,249 +1,806 @@ -package kha.js.vr; - -import js.Syntax; -import js.lib.Float32Array; -import kha.vr.Pose; -import kha.vr.PoseState; -import kha.vr.SensorState; -import kha.vr.TimeWarpParms; -import kha.math.FastMatrix4; -import kha.math.Vector3; -import kha.math.Quaternion; -import kha.SystemImpl; - -class VrInterface extends kha.vr.VrInterface { - var vrEnabled: Bool = false; - - var vrDisplay: Dynamic; - var frameData: Dynamic; - - var leftProjectionMatrix: FastMatrix4 = FastMatrix4.identity(); - var rightProjectionMatrix: FastMatrix4 = FastMatrix4.identity(); - var leftViewMatrix: FastMatrix4 = FastMatrix4.identity(); - var rightViewMatrix: FastMatrix4 = FastMatrix4.identity(); - - var width: Int = 0; - var height: Int = 0; - var vrWidth: Int = 0; - var vrHeight: Int = 0; - - public function new() { - super(); - #if kha_webvr - var displayEnabled: Bool = Syntax.code("navigator.getVRDisplays"); - #else - var displayEnabled = false; - #end - if (displayEnabled) { - vrEnabled = true; - getVRDisplays(); - trace("Display enabled."); - } - } - - function getVRDisplays() { - var vrDisplayInstance = Syntax.code("navigator.getVRDisplays()"); - vrDisplayInstance.then(function(displays) { - if (displays.length > 0) { - frameData = Syntax.code("new VRFrameData()"); - vrDisplay = Syntax.code("displays[0]"); - vrDisplay.depthNear = 0.1; - vrDisplay.depthFar = 1024.0; - - var leftEye = vrDisplay.getEyeParameters("left"); - var rightEye = vrDisplay.getEyeParameters("right"); - width = SystemImpl.khanvas.width; - height = SystemImpl.khanvas.height; - vrWidth = Std.int(Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2); - vrHeight = Std.int(Math.max(leftEye.renderHeight, rightEye.renderHeight)); - } - else { - trace("There are no VR displays connected."); - } - }); - } - - public override function onVRRequestPresent() { - try { - vrDisplay.requestPresent([{source: SystemImpl.khanvas}]).then(function() { - onResize(); - vrDisplay.requestAnimationFrame(onAnimationFrame); - }); - } - catch (err:Dynamic) { - trace("Failed to requestPresent."); - trace(err); - } - } - - public override function onVRExitPresent() { - try { - vrDisplay.exitPresent([{source: SystemImpl.khanvas}]).then(function() { - onResize(); - }); - } - catch (err:Dynamic) { - trace("Failed to exitPresent."); - trace(err); - } - } - - public override function onResetPose() { - try { - vrDisplay.resetPose(); - } - catch (err:Dynamic) { - trace("Failed to resetPose"); - trace(err); - } - } - - function onAnimationFrame(timestamp: Float): Void { - if (vrDisplay != null && vrDisplay.isPresenting) { - vrDisplay.requestAnimationFrame(onAnimationFrame); - - vrDisplay.getFrameData(frameData); - - leftProjectionMatrix = createMatrixFromArray(untyped frameData.leftProjectionMatrix); - leftViewMatrix = createMatrixFromArray(untyped frameData.leftViewMatrix); - - rightProjectionMatrix = createMatrixFromArray(untyped frameData.rightProjectionMatrix); - rightViewMatrix = createMatrixFromArray(untyped frameData.rightViewMatrix); - - // Submit the newly rendered layer to be presented by the VRDisplay - vrDisplay.submitFrame(); - } - } - - function onResize() { - if (vrDisplay != null && vrDisplay.isPresenting) { - SystemImpl.khanvas.width = vrWidth; - SystemImpl.khanvas.height = vrHeight; - } - else { - SystemImpl.khanvas.width = width; - SystemImpl.khanvas.height = height; - } - } - - public override function GetSensorState(): SensorState { - return GetPredictedSensorState(0.0); - } - - public override function GetPredictedSensorState(time: Float): SensorState { - var result: SensorState = new SensorState(); - - result.Predicted = new PoseState(); - result.Recorded = result.Predicted; - - result.Predicted.AngularAcceleration = new Vector3(); - result.Predicted.AngularVelocity = new Vector3(); - result.Predicted.LinearAcceleration = new Vector3(); - result.Predicted.LinearVelocity = new Vector3(); - result.Predicted.TimeInSeconds = time; - result.Predicted.Pose = new Pose(); - result.Predicted.Pose.Orientation = new Quaternion(); - result.Predicted.Pose.Position = new Vector3(); - - var mPose = frameData.pose; // predicted pose of the vrDisplay - if (mPose != null) { - result.Predicted.AngularVelocity = createVectorFromArray(untyped mPose.angularVelocity); - result.Predicted.AngularAcceleration = createVectorFromArray(untyped mPose.angularAcceleration); - result.Predicted.LinearVelocity = createVectorFromArray(untyped mPose.linearVelocity); - result.Predicted.LinearAcceleration = createVectorFromArray(untyped mPose.linearAcceleration); - result.Predicted.Pose.Orientation = createQuaternion(untyped mPose.orientation); - result.Predicted.Pose.Position = createVectorFromArray(untyped mPose.position); - } - - return result; - } - - // Sends a black image to the warp swap thread - public override function WarpSwapBlack(): Void { - // TODO: Implement - } - - // Sends the Oculus loading symbol to the warp swap thread - public override function WarpSwapLoadingIcon(): Void { - // TODO: Implement - } - - // Sends the set of images to the warp swap thread - public override function WarpSwap(parms: TimeWarpParms): Void { - // TODO: Implement - } - - public override function IsPresenting(): Bool { - if (vrDisplay != null) - return vrDisplay.isPresenting; - return false; - } - - public override function IsVrEnabled(): Bool { - return vrEnabled; - } - - public override function GetTimeInSeconds(): Float { - return Scheduler.time(); - } - - public override function GetProjectionMatrix(eye: Int): FastMatrix4 { - if (eye == 0) { - return leftProjectionMatrix; - } - else { - return rightProjectionMatrix; - } - } - - public override function GetViewMatrix(eye: Int): FastMatrix4 { - if (eye == 0) { - return leftViewMatrix; - } - else { - return rightViewMatrix; - } - } - - function createMatrixFromArray(array: Float32Array): FastMatrix4 { - var matrix: FastMatrix4 = FastMatrix4.identity(); - matrix._00 = array[0]; - matrix._01 = array[1]; - matrix._02 = array[2]; - matrix._03 = array[3]; - matrix._10 = array[4]; - matrix._11 = array[5]; - matrix._12 = array[6]; - matrix._13 = array[7]; - matrix._20 = array[8]; - matrix._21 = array[9]; - matrix._22 = array[10]; - matrix._23 = array[11]; - matrix._30 = array[12]; - matrix._31 = array[13]; - matrix._32 = array[14]; - matrix._33 = array[15]; - return matrix; - } - - function createVectorFromArray(array: Float32Array): Vector3 { - var vector: Vector3 = new Vector3(0, 0, 0); - if (array != null) { - vector.x = array[0]; - vector.y = array[1]; - vector.z = array[2]; - } - return vector; - } - - function createQuaternion(array: Float32Array): Quaternion { - var quaternion: Quaternion = new Quaternion(0, 0, 0, 0); - if (array != null) { - quaternion.x = array[0]; - quaternion.y = array[1]; - quaternion.z = array[2]; - quaternion.w = array[3]; - } - return quaternion; - } -} +package kha.js.vr; + +import js.Syntax; +import js.lib.Float32Array; +import kha.vr.Pose; +import kha.vr.PoseState; +import kha.vr.SensorState; +import kha.vr.TimeWarpParms; +import kha.math.FastMatrix4; +import kha.math.Vector3; +import kha.math.Quaternion; +import kha.SystemImpl; + +class VrInterface extends kha.vr.VrInterface { + var vrEnabled: Bool = false; + var isWebXR: Bool = false; + + var vrDisplay: Dynamic; + var frameData: Dynamic; + + var xrSession: Dynamic; + var xrRefSpace: Dynamic; + public var xrGLLayer: Dynamic; + public var currentFrame: Dynamic; + public var currentViews: Dynamic; + public var currentViewerPose: Dynamic; + public var currentInputSources: Dynamic; + var xrAnimationFrameHandle: Int = -1; + + public var _glContext: Dynamic; + public var _leftViewport: Dynamic; + public var _rightViewport: Dynamic; + public var _cachedViewsLength: Int = 0; + + var savedCanvasWidth: Int = 0; + var savedCanvasHeight: Int = 0; + var browserRAFId: Int = -1; + + var leftProjectionMatrix: FastMatrix4 = FastMatrix4.identity(); + var rightProjectionMatrix: FastMatrix4 = FastMatrix4.identity(); + var leftViewMatrix: FastMatrix4 = FastMatrix4.identity(); + var rightViewMatrix: FastMatrix4 = FastMatrix4.identity(); + + var width: Int = 0; + var height: Int = 0; + var vrWidth: Int = 0; + var vrHeight: Int = 0; + + public function new() { + super(); + #if kha_webvr + var webXREnabled: Bool = Syntax.code("navigator.xr"); + if (webXREnabled) { + isWebXR = true; + vrEnabled = true; + } + else { + var displayEnabled: Bool = Syntax.code("navigator.getVRDisplays"); + if (displayEnabled) { + isWebXR = false; + vrEnabled = true; + getVRDisplays(); + trace("WebVR 1.1 API detected"); + } + } + #else + var displayEnabled = false; + #end + if (displayEnabled) { + vrEnabled = true; + getVRDisplays(); + trace("Display enabled."); + } + } + + function getVRDisplays() { + var vrDisplayInstance = Syntax.code("navigator.getVRDisplays()"); + vrDisplayInstance.then(function(displays) { + if (displays.length > 0) { + frameData = Syntax.code("new VRFrameData()"); + vrDisplay = Syntax.code("displays[0]"); + vrDisplay.depthNear = 0.1; + vrDisplay.depthFar = 1024.0; + + var leftEye = vrDisplay.getEyeParameters("left"); + var rightEye = vrDisplay.getEyeParameters("right"); + width = SystemImpl.khanvas.width; + height = SystemImpl.khanvas.height; + vrWidth = Std.int(Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2); + vrHeight = Std.int(Math.max(leftEye.renderHeight, rightEye.renderHeight)); + } + else { + trace("There are no VR displays connected."); + } + }); + } + + public override function onVRRequestPresent() { + if (isWebXR) { + requestWebXRSession(); + } else { + // WebVR 1.1 + try { + vrDisplay.requestPresent([{source: SystemImpl.khanvas}]).then(function() { + onResize(); + vrDisplay.requestAnimationFrame(onAnimationFrame); + }); + } + catch (err:Dynamic) { + trace("Failed to requestPresent WebVR: " + err); + } + } + } + + function requestWebXRSession() { + var vrScaleFactor = 1.0; + #if lnx_vr + vrScaleFactor = leenkx.renderpath.Inc.getSuperSampling(); + trace("[VR] Using renderpath superSample as framebufferScaleFactor: " + vrScaleFactor); + #end + + try { + Syntax.code(" + + + let gl = null; + let canvas = null; + + try { + if (typeof kha_SystemImpl !== 'undefined') { + gl = kha_SystemImpl.gl; + canvas = kha_SystemImpl.khanvas; + } + } catch (e) { + trace('kha_SystemImpl access failed: ' + e.message; + } + + if (!canvas) { + canvas = document.querySelector('canvas'); + } + + if (canvas && !gl) { + const contextAttributes = { xrCompatible: true, antialias: true, alpha: false }; + gl = canvas.getContext('webgl2', contextAttributes) || + canvas.getContext('webgl', contextAttributes) || + canvas.getContext('experimental-webgl', contextAttributes); + } + + if (!canvas) { + canvas = document.getElementById('khanvas'); + if (canvas && !gl) { + gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); + } + } + + if (!gl) { + return; + } + const self = this; + const glContext = gl; + + self._glContext = glContext; + "); + + Syntax.code(" + self._vrRenderCallback = function() { + self.vrRenderCallback(); + }; + + const checkAndRequestSession = async () => { + try { + const supported = await navigator.xr.isSessionSupported('immersive-vr'); + if (!supported) { + trace('immersive-vr session not supported'); + } + } catch (e) { + trace('WARN: isSessionSupported failed: ' + e.message); + // Continue anyway as some browsers do not support the check itself + } + + return await navigator.xr.requestSession('immersive-vr', { + optionalFeatures: ['local-floor', 'hand-tracking', 'bounded-floor'] + }); + }; + + checkAndRequestSession().then(async (session) => { + self.xrSession = session; + if (typeof window !== 'undefined') { + window._khaSkipWindowRender = true; + } + + const contextAttributes = glContext.getContextAttributes(); + if (!contextAttributes || !contextAttributes.xrCompatible) { + await glContext.makeXRCompatible(); + } + self.xrGLLayer = new XRWebGLLayer(session, glContext, { + depth: true, // Essential for depth testing + stencil: false, // Not needed, wastes memory + alpha: false, // Not needed in VR wastes + antialias: true, // Smooth rendering + framebufferScaleFactor: {0} // VR resolution quality from renderpath + }); + + if (self.xrGLLayer.framebufferWidth === 0 || self.xrGLLayer.framebufferHeight === 0) { + trace('XRWebGLLayer framebuffer has invalid dimensions'); + } + + + session.updateRenderState({ + baseLayer: self.xrGLLayer + }); + const handlers = {}; + + handlers.end = () => { + self.onSessionEnd(); + }; + session.addEventListener('end', handlers.end); + + handlers.select = (event) => { + if (self.onSelect) self.onSelect(event); + }; + session.addEventListener('select', handlers.select); + + handlers.selectstart = (event) => { + if (self.onSelectStart) self.onSelectStart(event); + }; + session.addEventListener('selectstart', handlers.selectstart); + + handlers.selectend = (event) => { + if (self.onSelectEnd) self.onSelectEnd(event); + }; + session.addEventListener('selectend', handlers.selectend); + + handlers.squeeze = (event) => { + if (self.onSqueeze) self.onSqueeze(event); + }; + session.addEventListener('squeeze', handlers.squeeze); + + handlers.squeezestart = (event) => { + if (self.onSqueezeStart) self.onSqueezeStart(event); + }; + session.addEventListener('squeezestart', handlers.squeezestart); + + handlers.squeezeend = (event) => { + if (self.onSqueezeEnd) self.onSqueezeEnd(event); + }; + session.addEventListener('squeezeend', handlers.squeezeend); + + session.addEventListener('inputsourceschange', handlers.inputsourceschange); + + handlers.visibilitychange = (event) => { + const state = event.session.visibilityState; + + }; + session.addEventListener('visibilitychange', handlers.visibilitychange); + + self._eventHandlers = handlers; + + const requestRefSpace = async () => { + const spaces = ['local-floor', 'local']; + for (const space of spaces) { + try { + const refSpace = await session.requestReferenceSpace(space); + return refSpace; + } catch (e) { + trace(space + ' not supported'); + } + } + trace('No reference space supported'); + }; + + requestRefSpace().then((refSpace) => { + self.xrRefSpace = refSpace; + + if (canvas && canvas.width) { + self.savedCanvasWidth = canvas.width; + self.savedCanvasHeight = canvas.height; + } else { + const canvasFallback = document.querySelector('canvas'); + if (canvasFallback) { + self.savedCanvasWidth = canvasFallback.width; + self.savedCanvasHeight = canvasFallback.height; + } + } + + const onFrame = (time, frame) => { + try { + if (self.xrSession) { + self.xrAnimationFrameHandle = self.xrSession.requestAnimationFrame(onFrame); + } + + if (!self._lastFrameTime) self._lastFrameTime = time; + const deltaTime = time - self._lastFrameTime; + self._lastFrameTime = time; + + if (!window._xrFrameCount) window._xrFrameCount = 0; + window._xrFrameCount++; + + if (glContext && self.xrSession && self.xrSession.renderState && self.xrSession.renderState.baseLayer) { + const layer = self.xrSession.renderState.baseLayer; + if (layer.framebuffer) { + const pose = frame.getViewerPose(self.xrRefSpace); + if (pose && pose.views && pose.views.length > 0) { + glContext.bindFramebuffer(glContext.FRAMEBUFFER, layer.framebuffer); + + let bgR = 0, bgG = 0, bgB = 0; + if (typeof iron !== 'undefined' && iron.Scene && iron.Scene.active && iron.Scene.active.world && iron.Scene.active.world.raw) { + const bgColor = iron.Scene.active.world.raw.background_color; + if (bgColor !== undefined) { + bgR = ((bgColor >> 16) & 255) / 255; + bgG = ((bgColor >> 8) & 255) / 255; + bgB = (bgColor & 255) / 255; + } + } + + for (const view of pose.views) { + const vp = layer.getViewport(view); + glContext.viewport(vp.x, vp.y, vp.width, vp.height); + glContext.scissor(vp.x, vp.y, vp.width, vp.height); + glContext.enable(glContext.SCISSOR_TEST); + + glContext.clearColor(bgR, bgG, bgB, 1.0); + glContext.clear(glContext.COLOR_BUFFER_BIT | glContext.DEPTH_BUFFER_BIT); + + } + glContext.disable(glContext.SCISSOR_TEST); + } + } + } + + if (!self.xrSession) { + return; + } + + const pose = frame.getViewerPose(self.xrRefSpace); + if (!pose) { + return; + } + + if (pose.emulatedPosition && !self._emulatedPosLogged) { + self._emulatedPosLogged = true; + } + + const views = pose.views; + + if (!self.xrSession.renderState || !self.xrSession.renderState.baseLayer) { + if (!self._noRenderStateLogged) { + self._noRenderStateLogged = true; + } + return; + } + const glLayer = self.xrSession.renderState.baseLayer; + + if (!views || views.length === 0) { + return; + } + + if (self.xrSession.visibilityState === 'hidden') { + return; + } + + self.currentFrame = frame; + self.currentViews = views; + self.currentViewerPose = pose; / + if (self.xrSession && self.xrSession.inputSources) { + self.currentInputSources = self.xrSession.inputSources; + } + + if (glContext.isContextLost()) { + return; + } + + if (!glContext || !glLayer || !glLayer.framebuffer) { + return; + } + + if (glContext.bindVertexArray) { + glContext.bindVertexArray(null); + } + + while (glContext.getError() !== glContext.NO_ERROR) { + // Drain error queue + } + + glContext.bindFramebuffer(glContext.FRAMEBUFFER, glLayer.framebuffer); + + + const bindError = glContext.getError(); + if (bindError !== glContext.NO_ERROR && !self._bindErrorLogged) { + self._bindErrorLogged = true; + } + + const fbStatus = glContext.checkFramebufferStatus(glContext.FRAMEBUFFER); + if (fbStatus !== glContext.FRAMEBUFFER_COMPLETE) { + return; + } + + glContext.enable(glContext.DEPTH_TEST); + glContext.depthFunc(glContext.LEQUAL); + glContext.depthMask(true); + glContext.colorMask(true, true, true, true); + glContext.disable(glContext.BLEND); + glContext.enable(glContext.CULL_FACE); + glContext.cullFace(glContext.BACK); + glContext.frontFace(glContext.CCW); + glContext.disable(glContext.STENCIL_TEST); + glContext.disable(glContext.POLYGON_OFFSET_FILL); + + + if (!self._fbLogged && p) { + const depthTest = glContext.isEnabled(glContext.DEPTH_TEST); + const cullFace = glContext.isEnabled(glContext.CULL_FACE); + const blend = glContext.isEnabled(glContext.BLEND); + self._fbLogged = true; + } + + if (views.length === 0) { + return; + } + + if (views.length >= 1) { + try { + self._leftViewport = glLayer.getViewport(views[0]); + self._rightViewport = views.length >= 2 ? glLayer.getViewport(views[1]) : null; + self._cachedViewsLength = views.length; + + if (!self._leftViewport) { + return; + } + } catch (e) { + return; + } + } + + if (views.length >= 1) { + self.leftProjectionMatrix = self.createMatrixFromArray(views[0].projectionMatrix); + self.leftViewMatrix = self.createMatrixFromArray(views[0].transform.inverse.matrix); + } + if (views.length >= 2) { + self.rightProjectionMatrix = self.createMatrixFromArray(views[1].projectionMatrix); + self.rightViewMatrix = self.createMatrixFromArray(views[1].transform.inverse.matrix); + } else if (views.length === 1) { + self.rightProjectionMatrix = self.leftProjectionMatrix; + self.rightViewMatrix = self.leftViewMatrix; + } + + if (self._vrRenderCallback) { + self._vrRenderCallback(); + } + + } catch (err) { + console.error('XR Frame Error:', err); + } finally { + self.currentFrame = null; + self.currentViews = null; + self.currentInputSources = null; + } + }; + + self.xrAnimationFrameHandle = session.requestAnimationFrame(onFrame); + }).catch((err) => { + trace('REF SPACE FAILED: ' + err.message ); + }); + }).catch((err) => { + trace('SESSION FAILED: ' + err); + }); + ", vrScaleFactor); + } + catch (err:Dynamic) { + trace("Failed to requestSession (WebXR)."); + trace(err); + } + } + + function onSessionEnd() { + var canvas = SystemImpl.khanvas; + if (canvas == null) { + canvas = Syntax.code("document.querySelector('canvas')"); + } + + if (canvas != null && savedCanvasWidth > 0 && savedCanvasHeight > 0) { + canvas.width = savedCanvasWidth; + canvas.height = savedCanvasHeight; + } + + if (xrSession != null) { + Syntax.code(" + if (this.xrAnimationFrameHandle !== -1 && this.xrSession) { + this.xrSession.cancelAnimationFrame(this.xrAnimationFrameHandle); + this.xrAnimationFrameHandle = -1; + } + + if (this._eventHandlers && this.xrSession) { + const handlers = this._eventHandlers; + const events = ['end', 'select', 'selectstart', 'selectend', 'squeeze', 'squeezestart', 'squeezeend', 'inputsourceschange', 'visibilitychange']; + for (const event of events) { + if (handlers[event]) { + this.xrSession.removeEventListener(event, handlers[event]); + } + } + this._eventHandlers = null; + } + "); + } + + Syntax.code(" + const gl = this._glContext; + if (gl) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Restore default framebuffer + gl.disable(gl.SCISSOR_TEST); + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + } + + if (typeof window !== 'undefined') { + window._khaSkipWindowRender = false; + } + + this.xrSession = null; + this.xrRefSpace = null; + this.xrGLLayer = null; + this.currentFrame = null; + this.currentViews = null; + this.currentInputSources = null; + this._lastFrameTime = null; + + if (typeof window !== 'undefined') { + delete window._xrFrameCount; + delete window._ironRenderCount; + delete window._slowFrameCount; + } + "); + } + + public override function onVRExitPresent() { + if (isWebXR) { + try { + if (xrSession != null) { + Syntax.code(" + if (this.xrSession) { + this.xrSession.end().then(() => { + trace('Session ended'); + }).catch((err) => { + trace('Session.end() failed:', err); + }); + } + "); + xrSession = null; + xrRefSpace = null; + xrGLLayer = null; + } + } + catch (err:Dynamic) { + trace("Failed to exitPresent in WebXR"); + trace(err); + } + } + else { + // WebVR 1.1 + try { + vrDisplay.exitPresent([{source: SystemImpl.khanvas}]).then(function() { + onResize(); + }); + } + catch (err:Dynamic) { + trace("Failed to exitPresent"); + trace(err); + } + } + } + + public override function onResetPose() { + try { + vrDisplay.resetPose(); + } + catch (err:Dynamic) { + trace("Failed to resetPose"); + trace(err); + } + } + + function onAnimationFrame(timestamp: Float): Void { + if (vrDisplay != null && vrDisplay.isPresenting) { + vrDisplay.requestAnimationFrame(onAnimationFrame); + + vrDisplay.getFrameData(frameData); + + leftProjectionMatrix = createMatrixFromArray(untyped frameData.leftProjectionMatrix); + leftViewMatrix = createMatrixFromArray(untyped frameData.leftViewMatrix); + + rightProjectionMatrix = createMatrixFromArray(untyped frameData.rightProjectionMatrix); + rightViewMatrix = createMatrixFromArray(untyped frameData.rightViewMatrix); + + // Submit the newly rendered layer to be presented by the VRDisplay + vrDisplay.submitFrame(); + } + } + + function onResize() { + if (isWebXR) { + return; + } + else { + // WebVR 1.1 + if (vrDisplay != null && vrDisplay.isPresenting) { + var canvas = SystemImpl.khanvas; + if (canvas != null) { + canvas.width = vrWidth; + canvas.height = vrHeight; + } + } + else { + var canvas = SystemImpl.khanvas; + if (canvas != null) { + canvas.width = width; + canvas.height = height; + } + } + } + } + + public override function GetSensorState(): SensorState { + return GetPredictedSensorState(0.0); + } + + public override function GetPredictedSensorState(time: Float): SensorState { + var result: SensorState = new SensorState(); + + result.Predicted = new PoseState(); + result.Recorded = result.Predicted; + + result.Predicted.AngularAcceleration = new Vector3(); + result.Predicted.AngularVelocity = new Vector3(); + result.Predicted.LinearAcceleration = new Vector3(); + result.Predicted.LinearVelocity = new Vector3(); + result.Predicted.TimeInSeconds = time; + result.Predicted.Pose = new Pose(); + result.Predicted.Pose.Orientation = new Quaternion(); + result.Predicted.Pose.Position = new Vector3(); + + var mPose = frameData.pose; // predicted pose of the vrDisplay + if (mPose != null) { + result.Predicted.AngularVelocity = createVectorFromArray(untyped mPose.angularVelocity); + result.Predicted.AngularAcceleration = createVectorFromArray(untyped mPose.angularAcceleration); + result.Predicted.LinearVelocity = createVectorFromArray(untyped mPose.linearVelocity); + result.Predicted.LinearAcceleration = createVectorFromArray(untyped mPose.linearAcceleration); + result.Predicted.Pose.Orientation = createQuaternion(untyped mPose.orientation); + result.Predicted.Pose.Position = createVectorFromArray(untyped mPose.position); + } + + return result; + } + + // Sends a black image to the warp swap thread + public override function WarpSwapBlack(): Void { + // TODO: Implement + } + + // Sends the Oculus loading symbol to the warp swap thread + public override function WarpSwapLoadingIcon(): Void { + // TODO: Implement + } + + // Sends the set of images to the warp swap thread + public override function WarpSwap(parms: TimeWarpParms): Void { + // TODO: Implement + } + + public override function IsPresenting(): Bool { + var presenting = false; + if (vrDisplay != null) + presenting = vrDisplay.isPresenting; + } + return presenting; + } + + public override function IsVrEnabled(): Bool { + return vrEnabled; + } + + public override function GetTimeInSeconds(): Float { + return Scheduler.time(); + } + + public override function GetProjectionMatrix(eye: Int): FastMatrix4 { + if (eye == 0) { + return leftProjectionMatrix; + } + else { + return rightProjectionMatrix; + } + } + + public override function GetViewMatrix(eye: Int): FastMatrix4 { + if (eye == 0) { + return leftViewMatrix; + } + else { + return rightViewMatrix; + } + } + + function createMatrixFromArray(array: Float32Array): FastMatrix4 { + var matrix: FastMatrix4 = FastMatrix4.identity(); + if (array == null || array.length < 16) { + trace("Warning: Invalid matrix array, using identity"); + return matrix; + } + matrix._00 = array[0]; + matrix._01 = array[1]; + matrix._02 = array[2]; + matrix._03 = array[3]; + matrix._10 = array[4]; + matrix._11 = array[5]; + matrix._12 = array[6]; + matrix._13 = array[7]; + matrix._20 = array[8]; + matrix._21 = array[9]; + matrix._22 = array[10]; + matrix._23 = array[11]; + matrix._30 = array[12]; + matrix._31 = array[13]; + matrix._32 = array[14]; + matrix._33 = array[15]; + return matrix; + } + + function createVectorFromArray(array: Float32Array): Vector3 { + var vector: Vector3 = new Vector3(0, 0, 0); + if (array != null) { + vector.x = array[0]; + vector.y = array[1]; + vector.z = array[2]; + } + return vector; + } + + function createQuaternion(array: Float32Array): Quaternion { + var quaternion: Quaternion = new Quaternion(0, 0, 0, 0); + if (array != null) { + quaternion.x = array[0]; + quaternion.y = array[1]; + quaternion.z = array[2]; + quaternion.w = array[3]; + } + return quaternion; + } + + public function vrRenderCallback(): Void { + var g4 = kha.SystemImpl.frame != null ? kha.SystemImpl.frame.g4 : null; + + + if (g4 != null && iron.Scene.active != null && iron.RenderPath.active != null) { + + if (untyped window._vrUpdateStarted == null) { + untyped window._vrUpdateStarted = true; + } + + iron.system.Time.update(); + + iron.Scene.active.updateFrame(); + + + js.Syntax.code(" + const App = iron.App; + if (App) { + const frame = window._vrCallbackCount; + const inits = App.traitInits; + if (inits && inits.length > 0) { + for (let i = 0; i < inits.length; i++) { + inits[i](); + } + inits.length = 0; + } + + const fixedUpdates = App.traitFixedUpdates; + if (fixedUpdates) { + for (let i = 0; i < fixedUpdates.length; i++) { + fixedUpdates[i](); + } + } + + const updates = App.traitUpdates; + if (updates) { + for (let i = 0; i < updates.length; i++) { + updates[i](); + } + } + + const lateUpdates = App.traitLateUpdates; + if (lateUpdates) { + for (let i = 0; i < lateUpdates.length; i++) { + lateUpdates[i](); + } + } + + } + "); + iron.Scene.active.renderFrame(g4); + } + else { + if (untyped window._vrSkipLogged == null) { + untyped window._vrSkipLogged = true; + } + } + } +} diff --git a/Kha/Backends/Krom/Krom.hx b/Kha/Backends/Krom/Krom.hx index a6708de..f152c68 100644 --- a/Kha/Backends/Krom/Krom.hx +++ b/Kha/Backends/Krom/Krom.hx @@ -157,4 +157,5 @@ extern class Krom { static function getConstantLocationCompute(shader: Dynamic, name: String): Dynamic; static function getTextureUnitCompute(shader: Dynamic, name: String): Dynamic; static function compute(x: Int, y: Int, z: Int): Void; + static function viewportSetCamera(posX: Float, posY: Float, posZ: Float, rotX: Float, rotY: Float, rotZ: Float, rotW: Float): Void; }