static void UpdateOrAllocate(ref MeshHandle data, int vertexCount, int indexCount, UIRenderDevice device, out NativeSlice <Vertex> verts, out NativeSlice <UInt16> indices, out UInt16 indexOffset, ref ChainBuilderStats stats) { if (data != null) { // Try to fit within the existing allocation, optionally we can change the condition // to be an exact match of size to guarantee continuity in draw ranges if (data.allocVerts.size >= vertexCount && data.allocIndices.size >= indexCount) { device.Update(data, (uint)vertexCount, (uint)indexCount, out verts, out indices, out indexOffset); stats.updatedMeshAllocations++; } else { // Won't fit in the existing allocated region, free the current one device.Free(data); data = device.Allocate((uint)vertexCount, (uint)indexCount, out verts, out indices, out indexOffset); stats.newMeshAllocations++; } } else { data = device.Allocate((uint)vertexCount, (uint)indexCount, out verts, out indices, out indexOffset); stats.newMeshAllocations++; } }
public unsafe static UIRStylePainter.ClosingInfo PaintElement(RenderChain renderChain, VisualElement ve, ref ChainBuilderStats stats) { var device = renderChain.device; var isClippingWithStencil = ve.renderChainData.clipMethod == ClipMethod.Stencil; var isClippingWithScissors = ve.renderChainData.clipMethod == ClipMethod.Scissor; var isGroup = (ve.renderHints & RenderHints.GroupTransform) != 0; // Groups need to push view and scissors if ((UIRUtility.IsElementSelfHidden(ve) && !isClippingWithStencil && !isClippingWithScissors && !isGroup) || ve.renderChainData.isHierarchyHidden) { if (ve.renderChainData.data != null) { device.Free(ve.renderChainData.data); ve.renderChainData.data = null; } if (ve.renderChainData.firstCommand != null) { ResetCommands(renderChain, ve); } renderChain.ResetTextures(ve); return(new UIRStylePainter.ClosingInfo()); } // Retain our command insertion points if possible, to avoid paying the cost of finding them again RenderChainCommand oldCmdPrev = ve.renderChainData.firstCommand?.prev; RenderChainCommand oldCmdNext = ve.renderChainData.lastCommand?.next; RenderChainCommand oldClosingCmdPrev, oldClosingCmdNext; bool commandsAndClosingCommandsWereConsecutive = (ve.renderChainData.firstClosingCommand != null) && (oldCmdNext == ve.renderChainData.firstClosingCommand); if (commandsAndClosingCommandsWereConsecutive) { oldCmdNext = ve.renderChainData.lastClosingCommand.next; oldClosingCmdPrev = oldClosingCmdNext = null; } else { oldClosingCmdPrev = ve.renderChainData.firstClosingCommand?.prev; oldClosingCmdNext = ve.renderChainData.lastClosingCommand?.next; } Debug.Assert(oldCmdPrev?.owner != ve); Debug.Assert(oldCmdNext?.owner != ve); Debug.Assert(oldClosingCmdPrev?.owner != ve); Debug.Assert(oldClosingCmdNext?.owner != ve); ResetCommands(renderChain, ve); renderChain.ResetTextures(ve); k_GenerateEntries.Begin(); var painter = renderChain.painter; painter.Begin(ve); if (ve.visible) { painter.DrawVisualElementBackground(); painter.DrawVisualElementBorder(); painter.ApplyVisualElementClipping(); InvokeGenerateVisualContent(ve, painter.meshGenerationContext); } else { // Even though the element hidden, we still have to push the stencil shape or setup the scissors in case any children are visible. if (isClippingWithScissors || isClippingWithStencil) { painter.ApplyVisualElementClipping(); } } k_GenerateEntries.End(); MeshHandle data = ve.renderChainData.data; if (painter.totalVertices > device.maxVerticesPerPage) { Debug.LogError($"A {nameof(VisualElement)} must not allocate more than {device.maxVerticesPerPage } vertices."); if (data != null) { device.Free(data); data = null; } renderChain.ResetTextures(ve); // Restart without drawing anything. painter.Reset(); painter.Begin(ve); } // Convert entries to commands. var entries = painter.entries; if (entries.Count > 0) { NativeSlice <Vertex> verts = new NativeSlice <Vertex>(); NativeSlice <UInt16> indices = new NativeSlice <UInt16>(); UInt16 indexOffset = 0; if (painter.totalVertices > 0) { UpdateOrAllocate(ref data, painter.totalVertices, painter.totalIndices, device, out verts, out indices, out indexOffset, ref stats); } int vertsFilled = 0, indicesFilled = 0; RenderChainCommand cmdPrev = oldCmdPrev, cmdNext = oldCmdNext; if (oldCmdPrev == null && oldCmdNext == null) { FindCommandInsertionPoint(ve, out cmdPrev, out cmdNext); } // Vertex data, lazily computed bool vertexDataComputed = false; Matrix4x4 transform = Matrix4x4.identity; Color32 xformClipPages = new Color32(0, 0, 0, 0); Color32 ids = new Color32(0, 0, 0, 0); Color32 addFlags = new Color32(0, 0, 0, 0); Color32 opacityPage = new Color32(0, 0, 0, 0); Color32 textCoreSettingsPage = new Color32(0, 0, 0, 0); k_ConvertEntriesToCommandsMarker.Begin(); int firstDisplacementUV = -1, lastDisplacementUVPlus1 = -1; foreach (var entry in painter.entries) { if (entry.vertices.Length > 0 && entry.indices.Length > 0) { if (!vertexDataComputed) { vertexDataComputed = true; GetVerticesTransformInfo(ve, out transform); ve.renderChainData.verticesSpace = transform; // This is the space for the generated vertices below } Color32 transformData = renderChain.shaderInfoAllocator.TransformAllocToVertexData(ve.renderChainData.transformID); Color32 opacityData = renderChain.shaderInfoAllocator.OpacityAllocToVertexData(ve.renderChainData.opacityID); Color32 textCoreSettingsData = renderChain.shaderInfoAllocator.TextCoreSettingsToVertexData(ve.renderChainData.textCoreSettingsID); xformClipPages.r = transformData.r; xformClipPages.g = transformData.g; ids.r = transformData.b; opacityPage.r = opacityData.r; opacityPage.g = opacityData.g; ids.b = opacityData.b; if (entry.isTextEntry) { // It's important to avoid writing these values when the vertices aren't for text, // as these settings are shared with the vector graphics gradients. // The same applies to the CopyTransformVertsPos* methods below. textCoreSettingsPage.r = textCoreSettingsData.r; textCoreSettingsPage.g = textCoreSettingsData.g; ids.a = textCoreSettingsData.b; } Color32 clipRectData = renderChain.shaderInfoAllocator.ClipRectAllocToVertexData(entry.clipRectID); xformClipPages.b = clipRectData.r; xformClipPages.a = clipRectData.g; ids.g = clipRectData.b; addFlags.r = (byte)entry.addFlags; float textureId = entry.texture.ConvertToGpu(); // Copy vertices, transforming them as necessary var targetVerticesSlice = verts.Slice(vertsFilled, entry.vertices.Length); if (entry.uvIsDisplacement) { if (firstDisplacementUV < 0) { firstDisplacementUV = vertsFilled; lastDisplacementUVPlus1 = vertsFilled + entry.vertices.Length; } else if (lastDisplacementUVPlus1 == vertsFilled) { lastDisplacementUVPlus1 += entry.vertices.Length; } else { ve.renderChainData.disableNudging = true; // Disjoint displacement UV entries, we can't keep track of them, so disable nudging optimization altogether } } int entryIndexCount = entry.indices.Length; int entryIndexOffset = vertsFilled + indexOffset; var targetIndicesSlice = indices.Slice(indicesFilled, entryIndexCount); bool shapeWindingIsClockwise = UIRUtility.ShapeWindingIsClockwise(entry.maskDepth, entry.stencilRef); bool transformFlipsWinding = ve.renderChainData.worldFlipsWinding; var job = new ConvertMeshJobData { vertSrc = (IntPtr)entry.vertices.GetUnsafePtr(), vertDst = (IntPtr)targetVerticesSlice.GetUnsafePtr(), vertCount = targetVerticesSlice.Length, transform = transform, transformUVs = entry.uvIsDisplacement ? 1 : 0, xformClipPages = xformClipPages, ids = ids, addFlags = addFlags, opacityPage = opacityPage, textCoreSettingsPage = textCoreSettingsPage, isText = entry.isTextEntry ? 1 : 0, textureId = textureId, indexSrc = (IntPtr)entry.indices.GetUnsafePtr(), indexDst = (IntPtr)targetIndicesSlice.GetUnsafePtr(), indexCount = targetIndicesSlice.Length, indexOffset = entryIndexOffset, flipIndices = shapeWindingIsClockwise == transformFlipsWinding ? 1 : 0 }; renderChain.jobManager.Add(ref job); if (entry.isClipRegisterEntry) { painter.LandClipRegisterMesh(targetVerticesSlice, targetIndicesSlice, entryIndexOffset); } var cmd = InjectMeshDrawCommand(renderChain, ve, ref cmdPrev, ref cmdNext, data, entryIndexCount, indicesFilled, entry.material, entry.texture, entry.stencilRef); if (entry.isTextEntry) { // Set font atlas texture gradient scale cmd.state.sdfScale = entry.fontTexSDFScale; } vertsFilled += entry.vertices.Length; indicesFilled += entryIndexCount; } else if (entry.customCommand != null) { InjectCommandInBetween(renderChain, entry.customCommand, ref cmdPrev, ref cmdNext); } else { Debug.Assert(false); // Unable to determine what kind of command to generate here } } if (!ve.renderChainData.disableNudging && (firstDisplacementUV >= 0)) { ve.renderChainData.displacementUVStart = firstDisplacementUV; ve.renderChainData.displacementUVEnd = lastDisplacementUVPlus1; } k_ConvertEntriesToCommandsMarker.End(); } else if (data != null) { device.Free(data); data = null; } ve.renderChainData.data = data; if (painter.closingInfo.clipperRegisterIndices.Length == 0 && ve.renderChainData.closingData != null) { // No more closing data needed, so free it now device.Free(ve.renderChainData.closingData); ve.renderChainData.closingData = null; } if (painter.closingInfo.needsClosing) { k_GenerateClosingCommandsMarker.Begin(); RenderChainCommand cmdPrev = oldClosingCmdPrev, cmdNext = oldClosingCmdNext; if (commandsAndClosingCommandsWereConsecutive) { cmdPrev = ve.renderChainData.lastCommand; cmdNext = cmdPrev.next; } else if (cmdPrev == null && cmdNext == null) { FindClosingCommandInsertionPoint(ve, out cmdPrev, out cmdNext); } if (painter.closingInfo.PopDefaultMaterial) { var cmd = renderChain.AllocCommand(); cmd.type = CommandType.PopDefaultMaterial; cmd.closing = true; cmd.owner = ve; InjectClosingCommandInBetween(renderChain, cmd, ref cmdPrev, ref cmdNext); } if (painter.closingInfo.blitAndPopRenderTexture) { { var cmd = renderChain.AllocCommand(); cmd.type = CommandType.BlitToPreviousRT; cmd.closing = true; cmd.owner = ve; cmd.state.material = GetBlitMaterial(ve.subRenderTargetMode); Debug.Assert(cmd.state.material != null); InjectClosingCommandInBetween(renderChain, cmd, ref cmdPrev, ref cmdNext); } { var cmd = renderChain.AllocCommand(); cmd.type = CommandType.PopRenderTexture; cmd.closing = true; cmd.owner = ve; InjectClosingCommandInBetween(renderChain, cmd, ref cmdPrev, ref cmdNext); } } if (painter.closingInfo.clipperRegisterIndices.Length > 0) { var cmd = InjectClosingMeshDrawCommand(renderChain, ve, ref cmdPrev, ref cmdNext, null, 0, 0, null, TextureId.invalid, painter.closingInfo.maskStencilRef); painter.LandClipUnregisterMeshDrawCommand(cmd); // Placeholder command that will be filled actually later } if (painter.closingInfo.popViewMatrix) { var cmd = renderChain.AllocCommand(); cmd.type = CommandType.PopView; cmd.closing = true; cmd.owner = ve; InjectClosingCommandInBetween(renderChain, cmd, ref cmdPrev, ref cmdNext); } if (painter.closingInfo.popScissorClip) { var cmd = renderChain.AllocCommand(); cmd.type = CommandType.PopScissor; cmd.closing = true; cmd.owner = ve; InjectClosingCommandInBetween(renderChain, cmd, ref cmdPrev, ref cmdNext); } k_GenerateClosingCommandsMarker.End(); } // When we have a closing mesh, we must have an opening mesh. At least we assumed where we decide // whether we must nudge or not: we only test whether the opening mesh is non-null. Debug.Assert(ve.renderChainData.closingData == null || ve.renderChainData.data != null); var closingInfo = painter.closingInfo; painter.Reset(); return(closingInfo); }
static void DepthFirstOnVisualsChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, bool parentHierarchyHidden, bool hierarchical, ref ChainBuilderStats stats) { if (dirtyID == ve.renderChainData.dirtyID) { return; } ve.renderChainData.dirtyID = dirtyID; // Prevent reprocessing of the same element in the same pass if (hierarchical) { stats.recursiveVisualUpdatesExpanded++; } bool wasHierarchyHidden = ve.renderChainData.isHierarchyHidden; ve.renderChainData.isHierarchyHidden = parentHierarchyHidden || IsElementHierarchyHidden(ve); if (wasHierarchyHidden != ve.renderChainData.isHierarchyHidden) { hierarchical = true; } if (!hierarchical && (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.AllVisuals) == RenderDataDirtyTypes.VisualsOpacityId) { stats.opacityIdUpdates++; CommandGenerator.UpdateOpacityId(ve, renderChain); return; } UpdateWorldFlipsWinding(ve); Debug.Assert(ve.renderChainData.clipMethod != ClipMethod.Undetermined); Debug.Assert(RenderChainVEData.AllocatesID(ve.renderChainData.transformID) || ve.hierarchy.parent == null || ve.renderChainData.transformID.Equals(ve.hierarchy.parent.renderChainData.transformID) || (ve.renderHints & RenderHints.GroupTransform) != 0); if (ve is TextElement) { RenderEvents.UpdateTextCoreSettings(renderChain, ve); } UIRStylePainter.ClosingInfo closingInfo = CommandGenerator.PaintElement(renderChain, ve, ref stats); if (hierarchical) { // Recurse on children int childrenCount = ve.hierarchy.childCount; for (int i = 0; i < childrenCount; i++) { DepthFirstOnVisualsChanged(renderChain, ve.hierarchy[i], dirtyID, ve.renderChainData.isHierarchyHidden, true, ref stats); } } // By closing the element after its children, we can ensure closing data is allocated // at a time that would maintain continuity in the index buffer if (closingInfo.needsClosing) { CommandGenerator.ClosePaintElement(ve, closingInfo, renderChain, ref stats); } }
public unsafe static void ClosePaintElement(VisualElement ve, UIRStylePainter.ClosingInfo closingInfo, RenderChain renderChain, ref ChainBuilderStats stats) { if (closingInfo.clipperRegisterIndices.Length > 0) { NativeSlice <Vertex> verts = new NativeSlice <Vertex>(); NativeSlice <UInt16> indices = new NativeSlice <UInt16>(); UInt16 indexOffset = 0; // Due to device Update limitations, we cannot share the vertices of the registration mesh. It would be great // if we can just point winding-flipped indices towards the same vertices as the registration mesh. // For now, we duplicate the registration mesh entirely, wasting a bit of vertex memory UpdateOrAllocate(ref ve.renderChainData.closingData, closingInfo.clipperRegisterVertices.Length, closingInfo.clipperRegisterIndices.Length, renderChain.device, out verts, out indices, out indexOffset, ref stats); var job = new CopyClosingMeshJobData { vertSrc = (IntPtr)closingInfo.clipperRegisterVertices.GetUnsafePtr(), vertDst = (IntPtr)verts.GetUnsafePtr(), vertCount = verts.Length, indexSrc = (IntPtr)closingInfo.clipperRegisterIndices.GetUnsafePtr(), indexDst = (IntPtr)indices.GetUnsafePtr(), indexCount = indices.Length, indexOffset = indexOffset - closingInfo.clipperRegisterIndexOffset }; renderChain.jobManager.Add(ref job); closingInfo.clipUnregisterDrawCommand.mesh = ve.renderChainData.closingData; closingInfo.clipUnregisterDrawCommand.indexCount = indices.Length; } }
static void OnColorChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { if (dirtyID == ve.renderChainData.dirtyID) { return; } ve.renderChainData.dirtyID = dirtyID; // Prevent reprocessing of the same element in the same pass stats.colorUpdatesExpanded++; var newColor = ve.resolvedStyle.backgroundColor; ve.renderChainData.backgroundColor = newColor; bool shouldUpdateVisuals = false; if ((ve.renderHints & RenderHints.DynamicColor) == RenderHints.DynamicColor) { if (InitColorIDs(renderChain, ve)) { // New colors were allocated, we need to update the visuals shouldUpdateVisuals = true; } SetColorValues(renderChain, ve); if (ve is TextElement && !RenderEvents.UpdateTextCoreSettings(renderChain, ve)) { shouldUpdateVisuals = true; } } else { shouldUpdateVisuals = true; } if (shouldUpdateVisuals) { renderChain.UIEOnVisualsChanged(ve, false); } }
static void DepthFirstOnTransformOrSizeChanged(RenderChain renderChain, VisualElement parent, VisualElement ve, uint dirtyID, UIRenderDevice device, bool isAncestorOfChangeSkinned, bool transformChanged, ref ChainBuilderStats stats) { if (dirtyID == ve.renderChainData.dirtyID) { return; } stats.recursiveTransformUpdatesExpanded++; transformChanged |= (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.Transform) != 0; if (RenderChainVEData.AllocatesID(ve.renderChainData.clipRectID)) { renderChain.shaderInfoAllocator.SetClipRectValue(ve.renderChainData.clipRectID, GetClipRectIDClipInfo(ve)); } if (transformChanged && UpdateLocalFlipsWinding(ve)) { renderChain.UIEOnVisualsChanged(ve, true); } bool dirtyHasBeenResolved = true; if (RenderChainVEData.AllocatesID(ve.renderChainData.transformID)) { renderChain.shaderInfoAllocator.SetTransformValue(ve.renderChainData.transformID, GetTransformIDTransformInfo(ve)); isAncestorOfChangeSkinned = true; stats.boneTransformed++; } else if (!transformChanged) { // Only the clip info had to be updated, we can skip the other cases which are for transform changes only. } else if ((ve.renderHints & RenderHints.GroupTransform) != 0) { stats.groupTransformElementsChanged++; } else if (isAncestorOfChangeSkinned) { // Children of a bone element inherit the transform data change automatically when the root updates that data, no need to do anything for children Debug.Assert(RenderChainVEData.InheritsID(ve.renderChainData.transformID)); // The element MUST have a transformID that has been inherited from an ancestor dirtyHasBeenResolved = false; // We just skipped processing, if another later transform change is queued on this element this pass then we should still process it stats.skipTransformed++; } else if ((ve.renderChainData.dirtiedValues & (RenderDataDirtyTypes.Visuals | RenderDataDirtyTypes.VisualsHierarchy)) == 0 && (ve.renderChainData.data != null)) { // If a visual update will happen, then skip work here as the visual update will incorporate the transformed vertices if (!ve.renderChainData.disableNudging && CommandGenerator.NudgeVerticesToNewSpace(ve, renderChain, device)) { stats.nudgeTransformed++; } else { renderChain.UIEOnVisualsChanged(ve, false); // Nudging not allowed, so do a full visual repaint stats.visualUpdateTransformed++; } } if (dirtyHasBeenResolved) { ve.renderChainData.dirtyID = dirtyID; // Prevent reprocessing of the same element in the same pass } // Make sure to pre-evaluate world transform and clip now so we don't do it at render time if (renderChain.drawInCameras) { ve.EnsureWorldTransformAndClipUpToDate(); } if ((ve.renderHints & RenderHints.GroupTransform) == 0) { // Recurse on children int childrenCount = ve.hierarchy.childCount; for (int i = 0; i < childrenCount; i++) { DepthFirstOnTransformOrSizeChanged(renderChain, ve, ve.hierarchy[i], dirtyID, device, isAncestorOfChangeSkinned, transformChanged, ref stats); } } }
static void DepthFirstOnOpacityChanged(RenderChain renderChain, float parentCompositeOpacity, VisualElement ve, uint dirtyID, bool hierarchical, ref ChainBuilderStats stats, bool isDoingFullVertexRegeneration = false) { if (dirtyID == ve.renderChainData.dirtyID) { return; } ve.renderChainData.dirtyID = dirtyID; // Prevent reprocessing of the same element in the same pass stats.recursiveOpacityUpdatesExpanded++; float oldOpacity = ve.renderChainData.compositeOpacity; float newOpacity = ve.resolvedStyle.opacity * parentCompositeOpacity; const float meaningfullOpacityChange = 0.0001f; bool visiblityTresholdPassed = (oldOpacity < VisibilityTreshold ^ newOpacity < VisibilityTreshold); bool compositeOpacityChanged = Mathf.Abs(oldOpacity - newOpacity) > meaningfullOpacityChange || visiblityTresholdPassed; if (compositeOpacityChanged) { // Avoid updating cached opacity if it changed too little, because we don't want slow changes to // update the cache and never trigger the compositeOpacityChanged condition. // The only small change allowed is when we cross the "visible" boundary of VisibilityTreshold ve.renderChainData.compositeOpacity = newOpacity; } bool changedOpacityID = false; bool hasDistinctOpacity = newOpacity < parentCompositeOpacity - meaningfullOpacityChange; //assume 0 <= opacity <= 1 if (hasDistinctOpacity) { if (ve.renderChainData.opacityID.ownedState == OwnedState.Inherited) { changedOpacityID = true; ve.renderChainData.opacityID = renderChain.shaderInfoAllocator.AllocOpacity(); } if ((changedOpacityID || compositeOpacityChanged) && ve.renderChainData.opacityID.IsValid()) { renderChain.shaderInfoAllocator.SetOpacityValue(ve.renderChainData.opacityID, newOpacity); } } else if (ve.renderChainData.opacityID.ownedState == OwnedState.Inherited) { // Just follow my parent's alloc if (ve.hierarchy.parent != null && !ve.renderChainData.opacityID.Equals(ve.hierarchy.parent.renderChainData.opacityID)) { changedOpacityID = true; ve.renderChainData.opacityID = ve.hierarchy.parent.renderChainData.opacityID; ve.renderChainData.opacityID.ownedState = OwnedState.Inherited; } } else { // I have an owned allocation, but I must match my parent's opacity, just set the opacity rather than free and inherit our parent's if (compositeOpacityChanged && ve.renderChainData.opacityID.IsValid()) { renderChain.shaderInfoAllocator.SetOpacityValue(ve.renderChainData.opacityID, newOpacity); } } if (isDoingFullVertexRegeneration) { // A parent already called UIEOnVisualsChanged with hierarchical=true } else if (changedOpacityID && ((ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.Visuals) == 0) && (ve.renderChainData.data != null || ve.renderChainData.closingData != null)) { renderChain.UIEOnOpacityIdChanged(ve); // Changed opacity ID, must update vertices.. we don't do it hierarchical here since our children will go through this too } if (compositeOpacityChanged || changedOpacityID || hierarchical) { // Recurse on children int childrenCount = ve.hierarchy.childCount; for (int i = 0; i < childrenCount; i++) { DepthFirstOnOpacityChanged(renderChain, newOpacity, ve.hierarchy[i], dirtyID, hierarchical, ref stats, isDoingFullVertexRegeneration); } } }
internal static void ProcessOnVisualsChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { bool hierarchical = (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.VisualsHierarchy) != 0; if (hierarchical) { stats.recursiveVisualUpdates++; } else { stats.nonRecursiveVisualUpdates++; } var parent = ve.hierarchy.parent; var parentHierarchyHidden = parent != null && (parent.renderChainData.isHierarchyHidden || IsElementHierarchyHidden(parent)); DepthFirstOnVisualsChanged(renderChain, ve, dirtyID, parentHierarchyHidden, hierarchical, ref stats); }
internal static void ProcessOnTransformOrSizeChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { stats.recursiveTransformUpdates++; DepthFirstOnTransformOrSizeChanged(renderChain, ve.hierarchy.parent, ve, dirtyID, renderChain.device, false, false, ref stats); }
internal static void ProcessOnColorChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { stats.colorUpdates++; OnColorChanged(renderChain, ve, dirtyID, ref stats); }
internal static void ProcessOnOpacityChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { bool hierarchical = (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.OpacityHierarchy) != 0; stats.recursiveOpacityUpdates++; DepthFirstOnOpacityChanged(renderChain, ve.hierarchy.parent != null ? ve.hierarchy.parent.renderChainData.compositeOpacity : 1.0f, ve, dirtyID, hierarchical, ref stats); }
static void DepthFirstOnClippingChanged(RenderChain renderChain, VisualElement parent, VisualElement ve, uint dirtyID, bool hierarchical, bool isRootOfChange, // MUST be true on the root call. bool isPendingHierarchicalRepaint, // MUST be false on the root call. bool inheritedClipRectIDChanged, // MUST be false on the root call. bool inheritedMaskingChanged, // MUST be false on the root call. UIRenderDevice device, ref ChainBuilderStats stats) { bool upToDate = dirtyID == ve.renderChainData.dirtyID; if (upToDate && !inheritedClipRectIDChanged && !inheritedMaskingChanged) { return; } ve.renderChainData.dirtyID = dirtyID; // Prevent reprocessing of the same element in the same pass if (!isRootOfChange) { stats.recursiveClipUpdatesExpanded++; } isPendingHierarchicalRepaint |= (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.VisualsHierarchy) != 0; // Internal operations (done in this call) to do: bool mustUpdateClipRectID = hierarchical || isRootOfChange || inheritedClipRectIDChanged; bool mustUpdateClippingMethod = hierarchical || isRootOfChange; bool mustUpdateChildrenMasking = hierarchical || isRootOfChange || inheritedMaskingChanged; // External operations (done by recursion or postponed) to do: bool mustRepaintThis = false; bool mustRepaintHierarchy = false; bool mustProcessSizeChange = false; // mustRecurse implies recursing on all children, but doesn't force anything beyond them. // hierarchical implies recursing on all descendants // As a result, hierarchical implies mustRecurse bool mustRecurse = hierarchical; ClipMethod oldClippingMethod = ve.renderChainData.clipMethod; ClipMethod newClippingMethod = mustUpdateClippingMethod ? DetermineSelfClipMethod(renderChain, ve) : oldClippingMethod; // Shader discard support bool clipRectIDChanged = false; if (mustUpdateClipRectID) { BMPAlloc newClipRectID = ve.renderChainData.clipRectID; if (newClippingMethod == ClipMethod.ShaderDiscard) { if (!RenderChainVEData.AllocatesID(ve.renderChainData.clipRectID)) { newClipRectID = renderChain.shaderInfoAllocator.AllocClipRect(); if (!newClipRectID.IsValid()) { newClippingMethod = ClipMethod.Scissor; // Fallback to scissor since we couldn't allocate a clipRectID // Both shader discard and scisorring work with world-clip rectangles, so no need // to inherit any clipRectIDs for such elements, our own scissor rect clips up correctly newClipRectID = UIRVEShaderInfoAllocator.infiniteClipRect; } } } else { if (RenderChainVEData.AllocatesID(ve.renderChainData.clipRectID)) { renderChain.shaderInfoAllocator.FreeClipRect(ve.renderChainData.clipRectID); } // Inherit parent's clipRectID if possible. // Group transforms shouldn't inherit the clipRectID since they have a new frame of reference, // they provide a new baseline with the _PixelClipRect instead. if ((ve.renderHints & RenderHints.GroupTransform) == 0) { newClipRectID = ((newClippingMethod != ClipMethod.Scissor) && (parent != null)) ? parent.renderChainData.clipRectID : UIRVEShaderInfoAllocator.infiniteClipRect; newClipRectID.ownedState = OwnedState.Inherited; } } clipRectIDChanged = !ve.renderChainData.clipRectID.Equals(newClipRectID); Debug.Assert((ve.renderHints & RenderHints.GroupTransform) == 0 || !clipRectIDChanged); ve.renderChainData.clipRectID = newClipRectID; } bool maskingChanged = false; if (oldClippingMethod != newClippingMethod) { ve.renderChainData.clipMethod = newClippingMethod; if (oldClippingMethod == ClipMethod.Stencil || newClippingMethod == ClipMethod.Stencil) { maskingChanged = true; mustUpdateChildrenMasking = true; } if (oldClippingMethod == ClipMethod.Scissor || newClippingMethod == ClipMethod.Scissor) { // We need to add/remove scissor push/pop commands mustRepaintThis = true; } if (newClippingMethod == ClipMethod.ShaderDiscard || oldClippingMethod == ClipMethod.ShaderDiscard && RenderChainVEData.AllocatesID(ve.renderChainData.clipRectID)) { // We must update the clipping rects. mustProcessSizeChange = true; } } if (clipRectIDChanged) { // Our children MUST update their render data clipRectIDs mustRecurse = true; // Our children MUST update their vertex clipRectIDs mustRepaintHierarchy = true; } if (mustUpdateChildrenMasking) { int newChildrenMaskDepth = 0; int newChildrenStencilRef = 0; if (parent != null) { newChildrenMaskDepth = parent.renderChainData.childrenMaskDepth; newChildrenStencilRef = parent.renderChainData.childrenStencilRef; if (newClippingMethod == ClipMethod.Stencil) { if (newChildrenMaskDepth > newChildrenStencilRef) { ++newChildrenStencilRef; } ++newChildrenMaskDepth; } // When applying the MaskContainer hint, we skip because the last depth level because even though we // could technically increase the reference value, it would be useless since there won't be more // deeply nested masks that could benefit from it. if ((ve.renderHints & RenderHints.MaskContainer) == RenderHints.MaskContainer && newChildrenMaskDepth < UIRUtility.k_MaxMaskDepth) { newChildrenStencilRef = newChildrenMaskDepth; } } if (ve.renderChainData.childrenMaskDepth != newChildrenMaskDepth || ve.renderChainData.childrenStencilRef != newChildrenStencilRef) { maskingChanged = true; } ve.renderChainData.childrenMaskDepth = newChildrenMaskDepth; ve.renderChainData.childrenStencilRef = newChildrenStencilRef; } if (maskingChanged) { mustRecurse = true; // Our children must update their inherited state. // These optimizations would allow to skip repainting the hierarchy: // a) We could update the stencilRef in the commands without repainting // b) The winding order could be reversed without repainting (when required) // In the meantime, we have no other choice but to request a hierarchical repaint. mustRepaintHierarchy = true; } if ((mustRepaintThis || mustRepaintHierarchy) && !isPendingHierarchicalRepaint) { renderChain.UIEOnVisualsChanged(ve, mustRepaintHierarchy); isPendingHierarchicalRepaint = true; } if (mustProcessSizeChange) { renderChain.UIEOnTransformOrSizeChanged(ve, false, true); } if (mustRecurse) { int childrenCount = ve.hierarchy.childCount; for (int i = 0; i < childrenCount; i++) { DepthFirstOnClippingChanged( renderChain, ve, ve.hierarchy[i], dirtyID, // Having to recurse doesn't mean that we need to process ALL descendants. For example, the // propagation of the transformId may stop if a group or a bone is encountered. hierarchical, false, isPendingHierarchicalRepaint, clipRectIDChanged, maskingChanged, device, ref stats); } } }
internal static void ProcessOnClippingChanged(RenderChain renderChain, VisualElement ve, uint dirtyID, ref ChainBuilderStats stats) { bool hierarchical = (ve.renderChainData.dirtiedValues & RenderDataDirtyTypes.ClippingHierarchy) != 0; if (hierarchical) { stats.recursiveClipUpdates++; } else { stats.nonRecursiveClipUpdates++; } DepthFirstOnClippingChanged(renderChain, ve.hierarchy.parent, ve, dirtyID, hierarchical, true, false, false, false, renderChain.device, ref stats); }