package iron; import kha.Image; import kha.Color; import kha.Scheduler; import kha.graphics4.Graphics; import kha.graphics4.CubeMap; import kha.graphics4.DepthStencilFormat; import kha.graphics4.TextureFormat; import iron.system.Time; import iron.data.SceneFormat; import iron.data.MaterialData; import iron.data.ShaderData; import iron.data.ConstData; import iron.data.Data; import iron.object.Object; import iron.object.LightObject; import iron.object.MeshObject; import iron.object.Uniforms; import iron.object.Clipmap; #if lnx_vr import iron.math.Vec4; import iron.math.Mat4; import iron.math.Quat; #end class RenderPath { public static var active: RenderPath; #if lnx_vr static var vrSimulateMode: Bool = false; static var vrCameraOffsetSet:Bool = false; static var vrCameraOffset:Vec4 = new Vec4(); static var wasVRPresenting:Bool = false; public static var vrCalibrationPosition:Vec4 = null; public static var vrCalibrationRotation:iron.math.Quat = null; public static var vrCalibrationSaved:Bool = false; public static var vrCenterCameraWorld:Mat4 = null; static var vrOriginalSuperSample:Float = -1.0; public static inline function isVRPresenting(): Bool { #if (kha_webgl && lnx_vr) return kha.vr.VrInterface.instance != null && kha.vr.VrInterface.instance.IsPresenting(); #else return false; #end } public static inline function isVRSimulateMode(): Bool { return vrSimulateMode; } // TODO: done remove safely public static inline function debugLog(msg: String, once: Bool = true): Void { return; } #end public var frameScissor = false; public var frameScissorX = 0; public var frameScissorY = 0; public var frameScissorW = 0; public var frameScissorH = 0; public var frameTime = 0.0; public var frame = 0; public var currentTarget: RenderTarget = null; public var currentFace: Int; public var light: LightObject = null; public var sun: LightObject = null; public var point: LightObject = null; #if rp_probes public var currentProbeIndex = 0; #end public var isProbePlanar = false; public var isProbeCube = false; public var isProbe = false; public var currentG: Graphics = null; public var frameG: Graphics; #if lnx_vr var beginCalled = false; var scissorSet = false; var viewportScaled = false; var renderToXRFramebuffer = false; #end public var drawOrder = DrawOrder.Distance; public var paused = false; public var ready(get, never): Bool; function get_ready(): Bool { return loading == 0; } public var commands: Void->Void = null; public var setupDepthTexture: Void->Void = null; public var renderTargets: Map = new Map(); public var depthToRenderTarget: Map = new Map(); public var currentW: Int; public var currentH: Int; public var currentD: Int; var lastW = 0; var lastH = 0; var bindParams: Array; var meshesSorted: Bool; var scissorSet = false; var viewportScaled = false; var lastFrameTime = 0.0; var loading = 0; var cachedShaderContexts: Map = new Map(); var depthBuffers: Array<{name: String, format: String}> = []; var additionalTargets: Array; #if (rp_voxels != "Off") public static var pre_clear = true; public static var res_pre_clear = true; public static var clipmapLevel = 0; public static var clipmaps:Array; public static inline function getVoxelRes(): Int { #if (rp_voxelgi_resolution == 512) return 512; #elseif (rp_voxelgi_resolution == 256) return 256; #elseif (rp_voxelgi_resolution == 128) return 128; #elseif (rp_voxelgi_resolution == 64) return 64; #elseif (rp_voxelgi_resolution == 32) return 32; #elseif (rp_voxelgi_resolution == 16) return 16; #else return 0; #end } public static inline function getVoxelResZ(): Float { #if (rp_voxelgi_resolution_z == 1.0) return 1.0; #elseif (rp_voxelgi_resolution_z == 0.5) return 0.5; #elseif (rp_voxelgi_resolution_z == 0.25) return 0.25; #elseif (rp_voxelgi_resolution_z == 0.125) return 0.125; #else return 0.0; #end } #end #if lnx_debug public static var drawCalls = 0; public static var batchBuckets = 0; public static var batchCalls = 0; public static var culled = 0; public static var numTrisMesh = 0; public static var numTrisShadow = 0; #end public static function setActive(renderPath: RenderPath) { active = renderPath; } public function new() {} public function renderFrame(g: Graphics) { if (!ready || paused || iron.App.w() == 0 || iron.App.h() == 0) return; var appW = iron.App.w(); var appH = iron.App.h(); // use native XR framebuffer dimensions #if (kha_webgl && lnx_vr) if (kha.vr.VrInterface.instance != null) { var vr = kha.vr.VrInterface.instance; var isPresenting = vr != null && vr.IsPresenting(); // save/restore camera position between modes if (!wasVRPresenting && isPresenting) { if (Scene.active != null && Scene.active.camera != null) { if (vrCalibrationPosition == null) vrCalibrationPosition = new Vec4(); if (vrCalibrationRotation == null) vrCalibrationRotation = new Quat(); vrCalibrationPosition.setFrom(Scene.active.camera.transform.loc); vrCalibrationRotation.setFrom(Scene.active.camera.transform.rot); vrCalibrationSaved = true; } // save original super sampling for later vrOriginalSuperSample = leenkx.renderpath.Inc.superSample; // compositeToXR function handles blitting to VR framebuffer var xrVr: kha.js.vr.VrInterface = cast vr; if (xrVr.xrGLLayer != null) { var vrWidth = untyped xrVr.xrGLLayer.framebufferWidth; var vrHeight = untyped xrVr.xrGLLayer.framebufferHeight; } } else if (wasVRPresenting && !isPresenting) { // reset VR frame time before anything else #if (kha_webgl && lnx_vr) iron.system.Time.vrFrameTime = -1.0; #end if (vrCalibrationSaved && Scene.active != null && Scene.active.camera != null) { Scene.active.camera.transform.loc.setFrom(vrCalibrationPosition); Scene.active.camera.transform.rot.setFrom(vrCalibrationRotation); Scene.active.camera.buildMatrix(); Scene.active.camera.buildProjection(); } // restore original super sampling from simulate mode if (vrOriginalSuperSample >= 0.0) { leenkx.renderpath.Inc.superSample = vrOriginalSuperSample; for (rt in renderTargets) { if (rt.raw.width == 0 && rt.raw.scale != null) { rt.raw.scale = vrOriginalSuperSample; } } resize(); vrOriginalSuperSample = -1.0; } // reset offset for next session vrCameraOffsetSet = false; vrCameraOffset = null; } wasVRPresenting = isPresenting; if (isPresenting) { // TODO: re-investigate using super sampling to avoid pixelation in simulate mode while giving max quality in headset if (vrOriginalSuperSample >= 0.0 && leenkx.renderpath.Inc.superSample != 4.0) { leenkx.renderpath.Inc.superSample = 4.0; for (rt in renderTargets) { if (rt.raw.width == 0 && rt.raw.scale != null) { rt.raw.scale = 4.0; } } resize(); } var xrVr: kha.js.vr.VrInterface = cast vr; if (xrVr.xrGLLayer != null) { appW = xrVr.xrGLLayer.framebufferWidth; appH = xrVr.xrGLLayer.framebufferHeight; } } } #end if (lastW > 0 && (lastW != appW || lastH != appH)) resize(); lastW = appW; lastH = appH; frameTime = Time.time() - lastFrameTime; lastFrameTime = Time.time(); #if lnx_debug drawCalls = 0; batchBuckets = 0; batchCalls = 0; culled = 0; numTrisMesh = 0; numTrisShadow = 0; #end #if (rp_voxels != "Off") clipmapLevel = (clipmapLevel + 1) % Main.voxelgiClipmapCount; var clipmap = clipmaps[clipmapLevel]; clipmap.voxelSize = clipmaps[0].voxelSize * Math.pow(2.0, clipmapLevel); var texelSize = 2.0 * clipmap.voxelSize; var camera = iron.Scene.active.camera; var center = new iron.math.Vec3( Math.floor(camera.transform.worldx() / texelSize) * texelSize, Math.floor(camera.transform.worldy() / texelSize) * texelSize, Math.floor(camera.transform.worldz() / texelSize) * texelSize ); clipmap.offset_prev.x = Std.int((clipmap.center.x - center.x) / texelSize); clipmap.offset_prev.y = Std.int((clipmap.center.y - center.y) / texelSize); clipmap.offset_prev.z = Std.int((clipmap.center.z - center.z) / texelSize); clipmap.center = center; var res = getVoxelRes(); var resZ = getVoxelResZ(); var extents = new iron.math.Vec3(clipmap.voxelSize * res, clipmap.voxelSize * res, clipmap.voxelSize * resZ); if (clipmap.extents.x != extents.x || clipmap.extents.y != extents.y || clipmap.extents.z != extents.z) { pre_clear = true; } clipmap.extents = extents; #end // Render to screen or probe var cam = Scene.active.camera; isProbePlanar = cam != null && cam.renderTarget != null; isProbeCube = cam != null && cam.renderTargetCube != null; isProbe = isProbePlanar || isProbeCube; if (isProbePlanar) frameG = cam.renderTarget.g4; else if (isProbeCube) frameG = cam.renderTargetCube.g4; else frameG = g; currentW = iron.App.w(); currentH = iron.App.h(); currentD = 1; currentFace = -1; meshesSorted = false; for (l in Scene.active.lights) { if (l.visible) l.buildMatrix(Scene.active.camera); if (l.data.raw.type == "sun") sun = l; else point = l; } light = Scene.active.lights[0]; if (commands != null) { commands(); } if (!isProbe) frame++; } public function setTarget(target: String, additional: Array = null, viewportScale = 1.0) { if (target == "") { // Framebuffer currentD = 1; currentTarget = null; currentFace = -1; if (isProbeCube) { currentW = Scene.active.camera.renderTargetCube.width; currentH = Scene.active.camera.renderTargetCube.height; begin(frameG, Scene.active.camera.currentFace); } else { // Screen, planar probe currentW = kha.System.windowWidth(); currentH = kha.System.windowHeight(); if (frameScissor) setFrameScissor(); begin(frameG); if (!isProbe) { setCurrentViewport(kha.System.windowWidth(), kha.System.windowHeight()); setCurrentScissor(kha.System.windowWidth(), kha.System.windowHeight()); } } } else { // Render target var rt = renderTargets.get(target); currentTarget = rt; var additionalImages: Array = null; if (additional != null) { additionalImages = []; for (s in additional) { var t = renderTargets.get(s); additionalImages.push(t.image); } } var targetG = rt.isCubeMap ? rt.cubeMap.g4 : rt.image.g4; currentW = rt.isCubeMap ? rt.cubeMap.width : rt.image.width; currentH = rt.isCubeMap ? rt.cubeMap.height : rt.image.height; if (rt.is3D) currentD = rt.image.depth; begin(targetG, additionalImages, currentFace); } if (viewportScale != 1.0) { viewportScaled = true; var viewW = Std.int(currentW * viewportScale); var viewH = Std.int(currentH * viewportScale); currentG.viewport(0, viewH, viewW, viewH); currentG.scissor(0, viewH, viewW, viewH); } else if (viewportScaled) { // Reset viewport viewportScaled = false; setCurrentViewport(currentW, currentH); setCurrentScissor(currentW, currentH); } bindParams = null; } public function setDepthFrom(target: String, from: String) { var rt = renderTargets.get(target); rt.image.setDepthStencilFrom(renderTargets.get(from).image); } inline function begin(g: Graphics, additionalRenderTargets: Array = null, face = -1) { if (currentG != null) end(); currentG = g; additionalTargets = additionalRenderTargets; // we still bind but skip begin() when explicitly rendering to XR framebuffer (renderToXRFramebuffer flag) #if lnx_vr if (!renderToXRFramebuffer) { face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets); beginCalled = true; } else { // XR framebuffer is already bound by VrInterface so we dont rebind beginCalled = false; } #else face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets); #end } inline function end() { if (currentG == null) return; if (scissorSet) { currentG.disableScissor(); scissorSet = false; } #if lnx_vr if (beginCalled) { currentG.end(); beginCalled = false; } // persist for rendering both eyes if (!isVRPresenting()) { currentG = null; additionalTargets = null; } #else currentG.end(); currentG = null; #end bindParams = null; } public function setCurrentViewportWithOffset(viewW:Int, viewH:Int, offsetX: Int, offsetY: Int) { currentG.viewport(iron.App.x() + offsetX, currentH - viewH + iron.App.y() - offsetY, viewW, viewH); } public function setCurrentViewport(viewW: Int, viewH: Int) { currentG.viewport(iron.App.x(), currentH - (viewH - iron.App.y()), viewW, viewH); } public function setCurrentScissor(viewW: Int, viewH: Int) { currentG.scissor(iron.App.x(), currentH - (viewH - iron.App.y()), viewW, viewH); scissorSet = true; } public function setFrameScissor() { frameG.scissor(frameScissorX, currentH - (frameScissorH - frameScissorY), frameScissorW, frameScissorH); } public function setViewport(viewW: Int, viewH: Int) { setCurrentViewport(viewW, viewH); setCurrentScissor(viewW, viewH); } public function clearTarget(colorFlag: Null = null, depthFlag: Null = null) { if (colorFlag == -1) { // -1 == 0xffffffff if (Scene.active.world != null) { colorFlag = Scene.active.world.raw.background_color; } else if (Scene.active.camera != null) { var cc = Scene.active.camera.data.raw.clear_color; if (cc != null) colorFlag = kha.Color.fromFloats(cc[0], cc[1], cc[2]); } } currentG.clear(colorFlag, depthFlag, null); } public function clearImage(target: String, color: Int) { var rt = renderTargets.get(target); rt.image.clear(0, 0, 0, rt.image.width, rt.image.height, rt.image.depth, color); } public function generateMipmaps(target: String) { var rt = renderTargets.get(target); rt.image.generateMipmaps(1000); } static inline function boolToInt(b: Bool): Int { return b ? 1 : 0; } public static function sortMeshesDistance(meshes: Array) { meshes.sort(function(a, b): Int { #if rp_depth_texture var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead); if (depthDiff != 0) return depthDiff; #end return a.cameraDistance >= b.cameraDistance ? 1 : -1; }); } public static function sortMeshesIndex(meshes: Array) { meshes.sort(function(a, b): Int { #if rp_depth_texture var depthDiff = boolToInt(a.depthRead) - boolToInt(b.depthRead); if (depthDiff != 0) return depthDiff; #end if (a.data.sortingIndex != b.data.sortingIndex) { return a.data.sortingIndex > b.data.sortingIndex ? 1 : -1; } return a.data.name >= b.data.name ? 1 : -1; }); } public function drawMeshes(context: String) { var isShadows = context == "shadowmap"; if (isShadows) { // Disabled shadow casting for this light if (light == null || !light.data.raw.cast_shadow || !light.visible || light.data.raw.strength == 0) return; } // Single face attached if (currentFace >= 0 && light != null) light.setCubeFace(currentFace, Scene.active.camera); var drawn = false; #if lnx_csm if (isShadows && light.data.raw.type == "sun") { var step = currentH; // Atlas with tiles on x axis for (i in 0...LightObject.cascadeCount) { light.setCascade(Scene.active.camera, i); currentG.viewport(i * step, 0, step, step); submitDraw(context); } drawn = true; } #end #if lnx_clusters if (context == "mesh") LightObject.updateClusters(Scene.active.camera); #end if (!drawn) submitDraw(context); #if lnx_debug // Callbacks to specific context if (contextEvents != null) { var ar = contextEvents.get(context); if (ar != null) for (i in 0...ar.length) ar[i](currentG, i, ar.length); } #end end(); } @:access(iron.object.MeshObject) function submitDraw(context: String) { var camera = Scene.active.camera; var meshes = Scene.active.meshes; MeshObject.lastPipeline = null; if (!meshesSorted && camera != null) { // Order max once per frame for now var camX = camera.transform.worldx(); var camY = camera.transform.worldy(); var camZ = camera.transform.worldz(); for (mesh in meshes) { mesh.computeCameraDistance(camX, camY, camZ); mesh.computeDepthRead(); } #if lnx_batch sortMeshesDistance(Scene.active.meshBatch.nonBatched); #else drawOrder == DrawOrder.Index ? sortMeshesIndex(meshes) : sortMeshesDistance(meshes); #end meshesSorted = true; } #if lnx_batch Scene.active.meshBatch.render(currentG, context, bindParams); #else inline meshRenderLoop(currentG, context, bindParams, meshes); #end } static inline function meshRenderLoop(g: Graphics, context: String, _bindParams: Array, _meshes: Array) { var isReadingDepth = false; for (m in _meshes) { #if rp_depth_texture // First mesh that reads depth if (!isReadingDepth && m.depthRead) { if (context == "mesh") { // Copy the depth buffer so that we can read from it while writing active.setupDepthTexture(); } #if rp_depthprepass else if (context == "depth") { // Don't render in depth prepass break; } #end isReadingDepth = true; } #end m.render(g, context, _bindParams); } } #if lnx_debug static var contextEvents: MapInt->Int->Void>> = null; public static function notifyOnContext(name: String, onContext: Graphics->Int->Int->Void) { if (contextEvents == null) contextEvents = new Map(); var ar = contextEvents.get(name); if (ar == null) { ar = []; contextEvents.set(name, ar); } ar.push(onContext); } #end #if rp_decals public function drawDecals(context: String) { if (ConstData.boxVB == null) ConstData.createBoxData(); for (decal in Scene.active.decals) { decal.render(currentG, context, bindParams); } end(); } #end public function drawSkydome(handle: String) { if (ConstData.skydomeVB == null) ConstData.createSkydomeData(); var cc: CachedShaderContext = cachedShaderContexts.get(handle); if (cc.context == null) return; // World data not specified currentG.setPipeline(cc.context.pipeState); Uniforms.setContextConstants(currentG, cc.context, bindParams); Uniforms.setObjectConstants(currentG, cc.context, null); // External hosek #if lnx_deinterleaved currentG.setVertexBuffers(ConstData.skydomeVB); #else currentG.setVertexBuffer(ConstData.skydomeVB); #end currentG.setIndexBuffer(ConstData.skydomeIB); currentG.drawIndexedVertices(); end(); } #if rp_probes public function drawVolume(object: Object, handle: String) { if (ConstData.boxVB == null) ConstData.createBoxData(); var cc: CachedShaderContext = cachedShaderContexts.get(handle); currentG.setPipeline(cc.context.pipeState); Uniforms.setContextConstants(currentG, cc.context, bindParams); Uniforms.setObjectConstants(currentG, cc.context, object); currentG.setVertexBuffer(ConstData.boxVB); currentG.setIndexBuffer(ConstData.boxIB); currentG.drawIndexedVertices(); end(); } #end public function bindTarget(target: String, uniform: String) { if (bindParams != null) { bindParams.push(target); bindParams.push(uniform); } else bindParams = [target, uniform]; } // Full-screen triangle public function drawShader(handle: String) { // file/data_name/context var cc: CachedShaderContext = cachedShaderContexts.get(handle); if (ConstData.screenAlignedVB == null) ConstData.createScreenAlignedData(); currentG.setPipeline(cc.context.pipeState); Uniforms.setContextConstants(currentG, cc.context, bindParams); Uniforms.setObjectConstants(currentG, cc.context, null); currentG.setVertexBuffer(ConstData.screenAlignedVB); currentG.setIndexBuffer(ConstData.screenAlignedIB); currentG.drawIndexedVertices(); end(); } public function getComputeShader(handle: String): kha.compute.Shader { return Reflect.field(kha.Shaders, handle + "_comp"); } #if lnx_vr // blits to each eyes viewport in the XR framebuffer. public function compositeToXR(sourceTarget: String) { #if (kha_webgl && lnx_vr) var vr: kha.js.vr.VrInterface = cast kha.vr.VrInterface.instance; if (vr == null || vr._glContext == null || vr.xrGLLayer == null) { return; } var gl: js.html.webgl.WebGL2RenderingContext = cast vr._glContext; var source = renderTargets.get(sourceTarget); if (source == null) { return; } var sourceFB: js.html.webgl.Framebuffer = untyped source.image.g4.renderTargetFrameBuffer; if (sourceFB == null) { return; } // trace('Framebuffer OK'); renderToXRFramebuffer = true; gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER, vr.xrGLLayer.framebuffer); gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER, sourceFB); var readStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.READ_FRAMEBUFFER); var drawStatus = gl.checkFramebufferStatus(js.html.webgl.WebGL2RenderingContext.DRAW_FRAMEBUFFER); if (readStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE || drawStatus != js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER_COMPLETE) { return; } var halfWidth = Std.int(source.image.width / 2); var fullHeight = source.image.height; if (vr._leftViewport != null) { var vp = vr._leftViewport; gl.blitFramebuffer( 0, 0, halfWidth, fullHeight, vp.x, vp.y, vp.x + vp.width, vp.y + vp.height, js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT, js.html.webgl.WebGL2RenderingContext.LINEAR ); } if (vr._rightViewport != null) { var vp = vr._rightViewport; gl.blitFramebuffer( halfWidth, 0, source.image.width, fullHeight, vp.x, vp.y, vp.x + vp.width, vp.y + vp.height, js.html.webgl.WebGL2RenderingContext.COLOR_BUFFER_BIT, js.html.webgl.WebGL2RenderingContext.LINEAR ); } gl.bindFramebuffer(js.html.webgl.WebGL2RenderingContext.FRAMEBUFFER, null); renderToXRFramebuffer = false; #end } #end #if lnx_vr public function drawStereo(drawMeshes: Void->Void) { vrSimulateMode = false; if (currentG == null && frameG != null) { currentG = frameG; } var appw = iron.App.w(); var apph = iron.App.h(); var g = currentG; // get render target dimensions not App.w/h gbuffer is scaled in simulate mode with supersampling var gbuffer0 = renderTargets.get("gbuffer0"); var actualWidth = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.width : appw; var actualHeight = (gbuffer0 != null && gbuffer0.image != null) ? gbuffer0.image.height : apph; var actualHalfWidth = Std.int(actualWidth / 2); var vrFBWidth = actualWidth; var vrFBHeight = actualHeight; var vrHalfWidth = actualHalfWidth; var isVRPresenting = false; vrSimulateMode = false; var vr:Dynamic = null; var vrExists = false; #if (kha_webgl && lnx_vr) if (kha.vr.VrInterface.instance != null) { vr = kha.vr.VrInterface.instance; vrExists = true; } #end if (vrExists && vr != null && vr.IsPresenting()) { vrSimulateMode = false; isVRPresenting = true; // get framebuffer dimensions from XR layer #if (kha_webgl && lnx_vr) var xrVr: kha.js.vr.VrInterface = cast vr; if (xrVr.xrGLLayer != null) { vrFBWidth = untyped xrVr.xrGLLayer.framebufferWidth; vrFBHeight = untyped xrVr.xrGLLayer.framebufferHeight; vrHalfWidth = Std.int(vrFBWidth / 2); } #end if (Scene.active == null || Scene.active.camera == null) { return; } #if (kha_webgl && lnx_vr) if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity(); vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world); #end // LEFT EYE // HMD center for room scale position tracking #if (kha_webgl && lnx_vr) var xrVr: kha.js.vr.VrInterface = cast vr; if (xrVr.currentViewerPose != null) { var viewerTransform = untyped xrVr.currentViewerPose.transform; if (viewerTransform != null && viewerTransform.position != null) { // VR present calibration is used to position objects in world space not the camera var pos = viewerTransform.position; // camera follows headset directly in local floor space Scene.active.camera.transform.loc.set(pos.x, pos.y, pos.z); if (viewerTransform.orientation != null) { Scene.active.camera.transform.rot.set( viewerTransform.orientation.x, viewerTransform.orientation.y, viewerTransform.orientation.z, viewerTransform.orientation.w ); } Scene.active.camera.transform.buildMatrix(); } } iron.system.VRController.updatePoses(); #end Scene.active.camera.V.self = vr.GetViewMatrix(0); Scene.active.camera.P.self = vr.GetProjectionMatrix(0); Scene.active.camera.VP.setFrom(Scene.active.camera.P); Scene.active.camera.VP.multmat(Scene.active.camera.V); Scene.active.camera.buildMatrix(); // update frustum for culling var renderWidth = actualWidth; var renderHeight = actualHeight; var renderHalfWidth = actualHalfWidth; // left half of render target g.viewport(0, 0, renderHalfWidth, renderHeight); g.scissor(0, 0, renderHalfWidth, renderHeight); drawMeshes(); // RIGHT EYE Scene.active.camera.V.self = vr.GetViewMatrix(1); Scene.active.camera.P.self = vr.GetProjectionMatrix(1); Scene.active.camera.VP.setFrom(Scene.active.camera.P); Scene.active.camera.VP.multmat(Scene.active.camera.V); Scene.active.camera.buildMatrix(); // right half of render target g.viewport(renderHalfWidth, 0, renderHalfWidth, renderHeight); g.scissor(renderHalfWidth, 0, renderHalfWidth, renderHeight); drawMeshes(); // restore for post-processing g.disableScissor(); g.viewport(0, 0, renderWidth, renderHeight); } else { // Simulate vrSimulateMode = true; var ipd_offset = 0.032 * 35.0; #if (kha_webgl && lnx_vr) if (vrCenterCameraWorld == null) vrCenterCameraWorld = Mat4.identity(); vrCenterCameraWorld.setFrom(Scene.active.camera.transform.world); #end Scene.active.camera.buildProjection(actualHalfWidth / actualHeight); Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset); Scene.active.camera.buildMatrix(); g.viewport(0, 0, actualHalfWidth, actualHeight); g.scissor(0, 0, actualHalfWidth, actualHeight); drawMeshes(); begin(g, additionalTargets); Scene.active.camera.transform.move(Scene.active.camera.right(), ipd_offset * 2.0); Scene.active.camera.buildMatrix(); g.viewport(actualHalfWidth, 0, actualHalfWidth, actualHeight); g.scissor(actualHalfWidth, 0, actualHalfWidth, actualHeight); drawMeshes(); Scene.active.camera.transform.move(Scene.active.camera.right(), -ipd_offset); Scene.active.camera.buildMatrix(); g.disableScissor(); g.viewport(0, 0, actualWidth, actualHeight); } } #end public function loadShader(handle: String) { loading++; var cc: CachedShaderContext = cachedShaderContexts.get(handle); if (cc != null) { loading--; return; } cc = new CachedShaderContext(); cachedShaderContexts.set(handle, cc); // file/data_name/context var shaderPath = handle.split("/"); #if lnx_json shaderPath[0] += ".json"; #end Data.getShader(shaderPath[0], shaderPath[1], function(res: ShaderData) { cc.context = res.getContext(shaderPath[2]); loading--; }); } public function unloadShader(handle: String) { cachedShaderContexts.remove(handle); // file/data_name/context var shaderPath = handle.split("/"); // Todo: Handle context overrides (see Data.getShader()) Data.cachedShaders.remove(shaderPath[1]); } public function unload() { for (rt in renderTargets) rt.unload(); } public function resize() { if (kha.System.windowWidth() == 0 || kha.System.windowHeight() == 0) return; // Make sure depth buffer is attached to single target only and gets released once for (rt in renderTargets) { if (rt == null || rt.raw.width > 0 || rt.depthStencilFrom == "" || rt == depthToRenderTarget.get(rt.depthStencilFrom)) { continue; } var nodepth: RenderTarget = null; for (rt2 in renderTargets) { if (rt2 == null || rt2.raw.width > 0 || rt2.depthStencilFrom != "" || depthToRenderTarget.get(rt2.raw.depth_buffer) != null || rt2.raw.is_image == true) { continue; } nodepth = rt2; break; } if (nodepth != null) { rt.image.setDepthStencilFrom(nodepth.image); } } // Resize textures for (rt in renderTargets) { if (rt != null && rt.raw.width == 0) { App.notifyOnInit(rt.image.unload); rt.image = createImage(rt.raw, rt.depthStencil); } } // Attach depth buffers for (rt in renderTargets) { if (rt != null && rt.depthStencilFrom != "") { rt.image.setDepthStencilFrom(depthToRenderTarget.get(rt.depthStencilFrom).image); } } #if (rp_voxels != "Off") res_pre_clear = true; #end } public function createRenderTarget(t: RenderTargetRaw): RenderTarget { var rt = createTarget(t); renderTargets.set(t.name, rt); return rt; } public function createDepthBuffer(name: String, format: String = null) { depthBuffers.push({ name: name, format: format }); } function createTarget(t: RenderTargetRaw): RenderTarget { var rt = new RenderTarget(t); // With depth buffer if (t.depth_buffer != null) { rt.hasDepth = true; var depthTarget = depthToRenderTarget.get(t.depth_buffer); if (depthTarget == null) { // Create new one for (db in depthBuffers) { if (db.name == t.depth_buffer) { depthToRenderTarget.set(db.name, rt); rt.depthStencil = getDepthStencilFormat(db.format); rt.image = createImage(t, rt.depthStencil); break; } } } else { // Reuse rt.depthStencil = DepthStencilFormat.NoDepthAndStencil; rt.depthStencilFrom = t.depth_buffer; rt.image = createImage(t, rt.depthStencil); rt.image.setDepthStencilFrom(depthTarget.image); } } else { // No depth buffer rt.hasDepth = false; if (t.depth != null && t.depth > 1) rt.is3D = true; if (t.is_cubemap) { rt.isCubeMap = true; rt.depthStencil = DepthStencilFormat.NoDepthAndStencil; rt.cubeMap = createCubeMap(t, rt.depthStencil); } else { rt.depthStencil = DepthStencilFormat.NoDepthAndStencil; rt.image = createImage(t, rt.depthStencil); } } return rt; } function createImage(t: RenderTargetRaw, depthStencil: DepthStencilFormat): Image { var width = t.width == 0 ? iron.App.w() : t.width; var height = t.height == 0 ? iron.App.h() : t.height; var depth = t.depth != null ? t.depth : 0; if (t.displayp != null) { // 1080p/.. if (width > height) { width = Std.int(width * (t.displayp / height)); height = t.displayp; } else { height = Std.int(height * (t.displayp / width)); width = t.displayp; } } if (t.scale != null) { width = Std.int(width * t.scale); height = Std.int(height * t.scale); depth = Std.int(depth * t.scale); } if (width < 1) width = 1; if (height < 1) height = 1; if (t.depth != null && t.depth > 1) { // 3D texture // Image only var img = Image.create3D(width, height, depth, t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32); if (t.mipmaps) img.generateMipmaps(1000); // Allocate mipmaps return img; } else { // 2D texture if (t.is_image != null && t.is_image) { // Image var img = Image.create(width, height, t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32); if (t.mipmaps) img.generateMipmaps(1000); // Allocate mipmaps return img; } else { // Render target return Image.createRenderTarget(width, height, t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32, depthStencil); } } } function createCubeMap(t: RenderTargetRaw, depthStencil: DepthStencilFormat): CubeMap { return CubeMap.createRenderTarget(t.width, t.format != null ? getTextureFormat(t.format) : TextureFormat.RGBA32, depthStencil); } inline function getTextureFormat(s: String): TextureFormat { switch (s) { case "RGBA32": return TextureFormat.RGBA32; case "RGBA64": return TextureFormat.RGBA64; case "RGBA128": return TextureFormat.RGBA128; case "DEPTH16": return TextureFormat.DEPTH16; case "R32": return TextureFormat.A32; case "R16": return TextureFormat.A16; case "R8": return TextureFormat.L8; default: return TextureFormat.RGBA32; } } inline function getDepthStencilFormat(s: String): DepthStencilFormat { if (s == null || s == "") return DepthStencilFormat.DepthOnly; switch (s) { case "DEPTH24": return DepthStencilFormat.DepthOnly; case "DEPTH16": return DepthStencilFormat.Depth16; default: return DepthStencilFormat.DepthOnly; } } #if lnx_shadowmap_atlas // Allow setting a target with manual end() calling, this is to render multiple times to the same image (atlas) // TODO: allow manual end() calling in existing functions to prevent duplicated code public function setTargetStream(target:String, additional:Array = null, viewportScale = 1.0) { if (target == "") { // Framebuffer currentD = 1; currentTarget = null; currentFace = -1; if (isProbeCube) { currentW = Scene.active.camera.renderTargetCube.width; currentH = Scene.active.camera.renderTargetCube.height; beginStream(frameG, Scene.active.camera.currentFace); } else { // Screen, planar probe currentW = iron.App.w(); currentH = iron.App.h(); if (frameScissor) { setFrameScissor(); } beginStream(frameG); if (!isProbe) { setCurrentViewport(iron.App.w(), iron.App.h()); setCurrentScissor(iron.App.w(), iron.App.h()); } } } else { // Render target var rt = renderTargets.get(target); currentTarget = rt; var additionalImages:Array = null; if (additional != null) { additionalImages = []; for (s in additional) { var t = renderTargets.get(s); additionalImages.push(t.image); } } var targetG = rt.isCubeMap ? rt.cubeMap.g4 : rt.image.g4; currentW = rt.isCubeMap ? rt.cubeMap.width : rt.image.width; currentH = rt.isCubeMap ? rt.cubeMap.height : rt.image.height; if (rt.is3D) { currentD = rt.image.depth; } beginStream(targetG, additionalImages, currentFace); } if (viewportScale != 1.0) { viewportScaled = true; var viewW = Std.int(currentW * viewportScale); var viewH = Std.int(currentH * viewportScale); currentG.viewport(0, viewH, viewW, viewH); currentG.scissor(0, viewH, viewW, viewH); } else if (viewportScaled) { // Reset viewport viewportScaled = false; setCurrentViewport(currentW, currentH); setCurrentScissor(currentW, currentH); } bindParams = null; } inline function beginStream(g:Graphics, additionalRenderTargets:Array = null, face = -1) { currentG = g; additionalTargets = additionalRenderTargets; face >= 0 ? g.beginFace(face) : g.begin(additionalRenderTargets); } public function endStream() { if (scissorSet) { currentG.disableScissor(); scissorSet = false; } currentG.end(); currentG = null; bindParams = null; } public function drawMeshesStream(context:String) { // Single face attached if (currentFace >= 0 && light != null) { light.setCubeFace(currentFace, Scene.active.camera); } #if lnx_clusters if (context == "mesh") { LightObject.updateClusters(Scene.active.camera); } #end submitDraw(context); #if lnx_debug // Callbacks to specific context if (contextEvents != null) { var ar = contextEvents.get(context); if (ar != null) { for (i in 0...ar.length) { ar[i](currentG, i, ar.length); } } } #end } #end // lnx_shadowmap_atlas } class RenderTargetRaw { public var name: String; public var width: Int; public var height: Int; public var format: String = null; public var scale: Null = null; public var displayp: Null = null; // Set to 1080p/... public var depth_buffer: String = null; // 2D texture public var mipmaps: Null = null; public var depth: Null = null; // 3D texture public var is_image: Null = null; // Image public var is_cubemap: Null = null; // Cubemap public function new() {} } class RenderTarget { public var raw: RenderTargetRaw; public var depthStencil: DepthStencilFormat; public var depthStencilFrom = ""; public var image: Image = null; // RT or image public var cubeMap: CubeMap = null; public var hasDepth = false; public var is3D = false; // sampler2D / sampler3D public var isCubeMap = false; public function new(raw: RenderTargetRaw) { this.raw = raw; } public function unload() { if (image != null) image.unload(); if (cubeMap != null) cubeMap.unload(); } } class CachedShaderContext { public var context: ShaderContext; public function new() {} } @:enum abstract DrawOrder(Int) from Int { var Distance = 0; // Early-z var Index = 1; // Less state changes // var Mix = 2; // Distance buckets sorted by shader }