From 2f3e0c503a9f177e1bb7fe340bec71db43f2bf65 Mon Sep 17 00:00:00 2001 From: FurryR Date: Fri, 4 Oct 2024 10:39:11 +0800 Subject: [PATCH] Support off-screen culling Co-authored-by: Nightre Signed-off-by: FurryR --- src/Drawable.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/RenderWebGL.js | 26 ++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/Drawable.js b/src/Drawable.js index d9a2355f4..9f2e7e815 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -201,6 +201,50 @@ class Drawable { return this._visible; } + /** + * Returns whether this Drawable needs to be re-transformed before viewport culling. + * @returns {boolean} if the drawable needs to be transformed before viewport culling. + */ + needsTransformBeforeCulling () { + return this._rotationCenterDirty || this._skinScaleDirty; + } + + /** + * Check if the drawable is in the viewport. + * @param {number} halfNativeWidth Half width of the viewport. + * @param {number} halfNativeHeight Half height of the viewport. + * @returns {boolean} Whether the drawable is in the viewport. + */ + isInViewport (halfNativeWidth, halfNativeHeight) { + // Position of the texture + const positionX = Math.trunc(this._position[0] + 0.5 - this._rotationAdjusted[0]); + const positionY = Math.trunc(this._position[1] + 0.5 - this._rotationAdjusted[1]); + // Half of the texture size + const halfWidth = Math.trunc((this._skinScale[0] / 2) + 0.5); + const halfHeight = Math.trunc((this._skinScale[1] / 2) + 0.5); + + // The leftTop and rightBottomX of the sprite must be enlarged, + // otherwise there will be problems when rotating. + const maxHalfSize = Math.max(halfWidth, halfHeight); + + const leftTopX = positionX - halfWidth - maxHalfSize; + + // Y-axis is reversed + const leftTopY = positionY + halfHeight + maxHalfSize; + + const rightBottomX = positionX + halfWidth + maxHalfSize; + + // Y-axis is reversed + const rightBottomY = positionY - halfHeight - maxHalfSize; + + return !( + rightBottomX < -halfNativeWidth || + rightBottomY > halfNativeHeight || + leftTopX > halfNativeWidth || + leftTopY < -halfNativeHeight + ); + } + /** * Update the position if it is different. Marks the transform as dirty. * @param {Array.} position A new position. diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index a7a41f262..7aea9f0f1 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -2058,6 +2058,9 @@ class RenderWebGL extends EventEmitter { const gl = this._gl; let currentShader = null; + const halfViewportWidth = this._nativeSize[0] / 2; + const halfViewportHeight = this._nativeSize[1] / 2; + const framebufferSpaceScaleDiffers = ( 'framebufferWidth' in opts && 'framebufferHeight' in opts && opts.framebufferWidth !== this._nativeSize[0] && opts.framebufferHeight !== this._nativeSize[1] @@ -2071,12 +2074,29 @@ class RenderWebGL extends EventEmitter { if (opts.filter && !opts.filter(drawableID)) continue; const drawable = this._allDrawables[drawableID]; - /** @todo check if drawable is inside the viewport before anything else */ // Hidden drawables (e.g., by a "hide" block) are not drawn unless // the ignoreVisibility flag is used (e.g. for stamping or touchingColor). if (!drawable.getVisible() && !opts.ignoreVisibility) continue; + const uniforms = {}; + + // Check if the drawable is in the viewport. + if (drawMode === ShaderManager.DRAW_MODE.default && drawable.skin) { + let uniformTransformed = false; + if (drawable.needsTransformBeforeCulling()) { + Object.assign(uniforms, drawable.getUniforms()); + uniformTransformed = true; + } + + if (!drawable.isInViewport(halfViewportWidth, halfViewportHeight)) continue; + if (!uniformTransformed) { + Object.assign(uniforms, drawable.getUniforms()); + } + } else { + Object.assign(uniforms, drawable.getUniforms()); + } + // drawableScale is the "framebuffer-pixel-space" scale of the drawable, as percentages of the drawable's // "native size" (so 100 = same as skin's "native size", 200 = twice "native size"). // If the framebuffer dimensions are the same as the stage's "native" size, there's no need to calculate it. @@ -2091,7 +2111,6 @@ class RenderWebGL extends EventEmitter { // Skip private skins, if requested. if (opts.skipPrivateSkins && drawable.skin.private) continue; - const uniforms = {}; let effectBits = drawable.enabledEffects; effectBits &= Object.prototype.hasOwnProperty.call(opts, 'effectMask') ? opts.effectMask : effectBits; @@ -2112,8 +2131,7 @@ class RenderWebGL extends EventEmitter { } Object.assign(uniforms, - drawable.skin.getUniforms(drawableScale), - drawable.getUniforms()); + drawable.skin.getUniforms(drawableScale)); // Apply extra uniforms after the Drawable's, to allow overwriting. if (opts.extraUniforms) {