private void PaintSubTree(Event e, VisualElement root, Matrix4x4 offset, Rect currentGlobalClip) { if (root == null || root.panel != this) { return; } if ((root.pseudoStates & PseudoStates.Invisible) == PseudoStates.Invisible || root.style.opacity.GetSpecifiedValueOrDefault(1.0f) < Mathf.Epsilon) { return; } // update clip if (root.clippingOptions != VisualElement.ClippingOptions.NoClipping) { var worldBound = ComputeAAAlignedBound(root.rect, offset * root.worldTransform); // are we and our children clipped? if (!worldBound.Overlaps(currentGlobalClip)) { return; } float x1 = Mathf.Max(worldBound.x, currentGlobalClip.x); float x2 = Mathf.Min(worldBound.x + worldBound.width, currentGlobalClip.x + currentGlobalClip.width); float y1 = Mathf.Max(worldBound.y, currentGlobalClip.y); float y2 = Mathf.Min(worldBound.y + worldBound.height, currentGlobalClip.y + currentGlobalClip.height); // new global clip currentGlobalClip = new Rect(x1, y1, x2 - x1, y2 - y1); } else { //since our children are not clipped, there is no early out. } if (ShouldUsePixelCache(root)) { // now actually paint the texture to previous group IStylePainter painter = stylePainter; // validate cache texture size first var worldBound = root.worldBound; int w = (int)GUIUtility.Internal_Roundf(worldBound.xMax) - (int)GUIUtility.Internal_Roundf(worldBound.xMin); int h = (int)GUIUtility.Internal_Roundf(worldBound.yMax) - (int)GUIUtility.Internal_Roundf(worldBound.yMin); // This needs to be consistent with RoundRect() in GUITexture.cpp. Otherwise, the texture may be stretched. int textureWidth = (int)GUIUtility.Internal_Roundf(w * GUIUtility.pixelsPerPoint); int textureHeight = (int)GUIUtility.Internal_Roundf(h * GUIUtility.pixelsPerPoint); // Prevent the texture size from going empty, which may occur if the element has a sub-pixel size textureWidth = Math.Max(textureWidth, 1); textureHeight = Math.Max(textureHeight, 1); var cache = root.renderData.pixelCache; if (cache != null && (cache.width != textureWidth || cache.height != textureHeight) && (!keepPixelCacheOnWorldBoundChange || root.IsDirty(ChangeType.Repaint))) { Object.DestroyImmediate(cache); cache = root.renderData.pixelCache = null; } // if the child node world transforms are not up to date due to changes below the pixel cache this is fine. if (root.IsDirty(ChangeType.Repaint) || root.renderData.pixelCache == null || !root.renderData.pixelCache.IsCreated()) { // Recreate as needed if (cache == null) { root.renderData.pixelCache = cache = new RenderTexture( textureWidth, textureHeight, 32, // depth RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); } bool hasRoundedBorderRects = (root.style.borderTopLeftRadius > 0 || root.style.borderTopRightRadius > 0 || root.style.borderBottomLeftRadius > 0 || root.style.borderBottomRightRadius > 0); RenderTexture temporaryTexture = null; var old = RenderTexture.active; try { // We first render to a temp texture, then blit the result into the result pixelCache again to mask the rounder corners if (hasRoundedBorderRects) { temporaryTexture = cache = RenderTexture.GetTemporary(textureWidth, textureHeight, 32, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); } // render the texture again to clip the round rect borders RenderTexture.active = cache; GL.Clear(true, true, new Color(0, 0, 0, 0)); // Calculate the offset required to translate the origin of the rect to the upper left corner // of the pixel cache. We need to round because the rect will be rounded when rendered. var childrenOffset = Matrix4x4.Translate(new Vector3(-GUIUtility.Internal_Roundf(worldBound.x), -GUIUtility.Internal_Roundf(worldBound.y), 0)); Matrix4x4 offsetWorldTransform = childrenOffset * root.worldTransform; // reset clipping var textureClip = new Rect(0, 0, w, h); painter.currentTransform = offsetWorldTransform; // Metal ignores the sRGBWrite flag and will always do linear to gamma conversions // when writing to an sRGB buffer. In this situation, we disable the manual sRGB // conversion since it will be done automatically when writing to the framebuffer. bool manualTex2SRGBEnabled = (SystemInfo.graphicsDeviceType != GraphicsDeviceType.Metal); using (new GUIUtility.ManualTex2SRGBScope(manualTex2SRGBEnabled)) using (new GUIClip.ParentClipScope(painter.currentTransform, textureClip)) { // Paint self painter.currentWorldClip = textureClip; root.DoRepaint(painter); root.ClearDirty(ChangeType.Repaint); PaintSubTreeChildren(e, root, childrenOffset, textureClip); } if (hasRoundedBorderRects) { RenderTexture.active = root.renderData.pixelCache; // Fix up transform for subtree to match texture upper left painter.currentTransform = Matrix4x4.identity; using (new GUIUtility.ManualTex2SRGBScope(manualTex2SRGBEnabled)) using (new GUIClip.ParentClipScope(painter.currentTransform, textureClip)) { GL.Clear(true, true, new Color(0, 0, 0, 0)); var textureParams = painter.GetDefaultTextureParameters(root); textureParams.texture = cache; textureParams.scaleMode = ScaleMode.StretchToFill; textureParams.rect = textureClip; textureParams.border.SetWidth(0.0f); // The rect of the temporary texture implicitly carries the scale factor of the // transform. Since we are blitting with an identity matrix, we need to scale the // radius manually. // We assume uniform positive scaling without rotations. Vector4 toScale = new Vector4(1, 0, 0, 0); Vector4 scaled = offsetWorldTransform * toScale; float radiusScale = scaled.x; textureParams.border.SetRadius( textureParams.border.topLeftRadius * radiusScale, textureParams.border.topRightRadius * radiusScale, textureParams.border.bottomRightRadius * radiusScale, textureParams.border.bottomLeftRadius * radiusScale); // No border is drawn but the rounded corners are clipped properly. // Use premultiply alpha to avoid blending again. textureParams.usePremultiplyAlpha = true; painter.DrawTexture(textureParams); } // Redraw the border (border was already drawn in first root.DoRepaint call). painter.currentTransform = offsetWorldTransform; using (new GUIUtility.ManualTex2SRGBScope(manualTex2SRGBEnabled)) using (new GUIClip.ParentClipScope(painter.currentTransform, textureClip)) { painter.DrawBorder(root); } } } finally { cache = null; if (temporaryTexture != null) { RenderTexture.ReleaseTemporary(temporaryTexture); } RenderTexture.active = old; } } // now actually paint the texture to previous group painter.currentWorldClip = currentGlobalClip; painter.currentTransform = offset * root.worldTransform; var painterParams = new TextureStylePainterParameters { rect = root.alignedRect, uv = new Rect(0, 0, 1, 1), texture = root.renderData.pixelCache, color = Color.white, scaleMode = ScaleMode.StretchToFill, usePremultiplyAlpha = true }; using (new GUIClip.ParentClipScope(painter.currentTransform, currentGlobalClip)) { painter.DrawTexture(painterParams); } } else { stylePainter.currentTransform = offset * root.worldTransform; using (new GUIClip.ParentClipScope(stylePainter.currentTransform, currentGlobalClip)) { stylePainter.currentWorldClip = currentGlobalClip; stylePainter.mousePosition = root.worldTransform.inverse.MultiplyPoint3x4(e.mousePosition); stylePainter.opacity = root.style.opacity.GetSpecifiedValueOrDefault(1.0f); root.DoRepaint(stylePainter); stylePainter.opacity = 1.0f; root.ClearDirty(ChangeType.Repaint); PaintSubTreeChildren(e, root, offset, currentGlobalClip); } } }