package com.mojang.blaze3d.framegraph; import com.mojang.blaze3d.framegraph.FrameGraphBuilder.Inspector.1; import com.mojang.blaze3d.resource.GraphicsResourceAllocator; import com.mojang.blaze3d.resource.ResourceDescriptor; import com.mojang.blaze3d.resource.ResourceHandle; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class FrameGraphBuilder { private final List> internalResources = new ArrayList(); private final List> externalResources = new ArrayList(); private final List passes = new ArrayList(); public FramePass addPass(String name) { FrameGraphBuilder.Pass pass = new FrameGraphBuilder.Pass(this.passes.size(), name); this.passes.add(pass); return pass; } public ResourceHandle importExternal(String name, T resource) { FrameGraphBuilder.ExternalResource externalResource = new FrameGraphBuilder.ExternalResource<>(name, null, resource); this.externalResources.add(externalResource); return externalResource.handle; } public ResourceHandle createInternal(String name, ResourceDescriptor descriptor) { return this.createInternalResource(name, descriptor, null).handle; } FrameGraphBuilder.InternalVirtualResource createInternalResource( String name, ResourceDescriptor descriptor, @Nullable FrameGraphBuilder.Pass createdBy ) { int i = this.internalResources.size(); FrameGraphBuilder.InternalVirtualResource internalVirtualResource = new FrameGraphBuilder.InternalVirtualResource<>(i, name, createdBy, descriptor); this.internalResources.add(internalVirtualResource); return internalVirtualResource; } public void execute(GraphicsResourceAllocator allocator) { this.execute(allocator, FrameGraphBuilder.Inspector.NONE); } public void execute(GraphicsResourceAllocator allocator, FrameGraphBuilder.Inspector inspector) { BitSet bitSet = this.identifyPassesToKeep(); List list = new ArrayList(bitSet.cardinality()); BitSet bitSet2 = new BitSet(this.passes.size()); for (FrameGraphBuilder.Pass pass : this.passes) { this.resolvePassOrder(pass, bitSet, bitSet2, list); } this.assignResourceLifetimes(list); for (FrameGraphBuilder.Pass pass : list) { for (FrameGraphBuilder.InternalVirtualResource internalVirtualResource : pass.resourcesToAcquire) { inspector.acquireResource(internalVirtualResource.name); internalVirtualResource.acquire(allocator); } inspector.beforeExecutePass(pass.name); pass.task.run(); inspector.afterExecutePass(pass.name); for (int i = pass.resourcesToRelease.nextSetBit(0); i >= 0; i = pass.resourcesToRelease.nextSetBit(i + 1)) { FrameGraphBuilder.InternalVirtualResource internalVirtualResource = (FrameGraphBuilder.InternalVirtualResource)this.internalResources.get(i); inspector.releaseResource(internalVirtualResource.name); internalVirtualResource.release(allocator); } } } private BitSet identifyPassesToKeep() { Deque deque = new ArrayDeque(this.passes.size()); BitSet bitSet = new BitSet(this.passes.size()); for (FrameGraphBuilder.VirtualResource virtualResource : this.externalResources) { FrameGraphBuilder.Pass pass = virtualResource.handle.createdBy; if (pass != null) { this.discoverAllRequiredPasses(pass, bitSet, deque); } } for (FrameGraphBuilder.Pass pass2 : this.passes) { if (pass2.disableCulling) { this.discoverAllRequiredPasses(pass2, bitSet, deque); } } return bitSet; } private void discoverAllRequiredPasses(FrameGraphBuilder.Pass pass, BitSet passesToKeep, Deque output) { output.add(pass); while (!output.isEmpty()) { FrameGraphBuilder.Pass pass2 = (FrameGraphBuilder.Pass)output.poll(); if (!passesToKeep.get(pass2.id)) { passesToKeep.set(pass2.id); for (int i = pass2.requiredPassIds.nextSetBit(0); i >= 0; i = pass2.requiredPassIds.nextSetBit(i + 1)) { output.add((FrameGraphBuilder.Pass)this.passes.get(i)); } } } } private void resolvePassOrder(FrameGraphBuilder.Pass pass, BitSet passesToKeep, BitSet output, List orderedPasses) { if (output.get(pass.id)) { String string = (String)output.stream().mapToObj(ix -> ((FrameGraphBuilder.Pass)this.passes.get(ix)).name).collect(Collectors.joining(", ")); throw new IllegalStateException("Frame graph cycle detected between " + string); } else if (passesToKeep.get(pass.id)) { output.set(pass.id); passesToKeep.clear(pass.id); for (int i = pass.requiredPassIds.nextSetBit(0); i >= 0; i = pass.requiredPassIds.nextSetBit(i + 1)) { this.resolvePassOrder((FrameGraphBuilder.Pass)this.passes.get(i), passesToKeep, output, orderedPasses); } for (FrameGraphBuilder.Handle handle : pass.writesFrom) { for (int j = handle.readBy.nextSetBit(0); j >= 0; j = handle.readBy.nextSetBit(j + 1)) { if (j != pass.id) { this.resolvePassOrder((FrameGraphBuilder.Pass)this.passes.get(j), passesToKeep, output, orderedPasses); } } } orderedPasses.add(pass); output.clear(pass.id); } } private void assignResourceLifetimes(Collection passes) { FrameGraphBuilder.Pass[] passs = new FrameGraphBuilder.Pass[this.internalResources.size()]; for (FrameGraphBuilder.Pass pass : passes) { for (int i = pass.requiredResourceIds.nextSetBit(0); i >= 0; i = pass.requiredResourceIds.nextSetBit(i + 1)) { FrameGraphBuilder.InternalVirtualResource internalVirtualResource = (FrameGraphBuilder.InternalVirtualResource)this.internalResources.get(i); FrameGraphBuilder.Pass pass2 = passs[i]; passs[i] = pass; if (pass2 == null) { pass.resourcesToAcquire.add(internalVirtualResource); } else { pass2.resourcesToRelease.clear(i); } pass.resourcesToRelease.set(i); } } } @Environment(EnvType.CLIENT) static class ExternalResource extends FrameGraphBuilder.VirtualResource { private final T resource; public ExternalResource(String name, @Nullable FrameGraphBuilder.Pass createdBy, T resource) { super(name, createdBy); this.resource = resource; } @Override public T get() { return this.resource; } } @Environment(EnvType.CLIENT) static class Handle implements ResourceHandle { final FrameGraphBuilder.VirtualResource holder; private final int version; @Nullable final FrameGraphBuilder.Pass createdBy; final BitSet readBy = new BitSet(); @Nullable private FrameGraphBuilder.Handle aliasedBy; Handle(FrameGraphBuilder.VirtualResource holder, int version, @Nullable FrameGraphBuilder.Pass createdBy) { this.holder = holder; this.version = version; this.createdBy = createdBy; } @Override public T get() { return this.holder.get(); } FrameGraphBuilder.Handle writeAndAlias(FrameGraphBuilder.Pass alias) { if (this.holder.handle != this) { throw new IllegalStateException("Handle " + this + " is no longer valid, as its contents were moved into " + this.aliasedBy); } else { FrameGraphBuilder.Handle handle = new FrameGraphBuilder.Handle<>(this.holder, this.version + 1, alias); this.holder.handle = handle; this.aliasedBy = handle; return handle; } } public String toString() { return this.createdBy != null ? this.holder + "#" + this.version + " (from " + this.createdBy + ")" : this.holder + "#" + this.version; } } @Environment(EnvType.CLIENT) public interface Inspector { FrameGraphBuilder.Inspector NONE = new 1(); default void acquireResource(String name) { } default void releaseResource(String name) { } default void beforeExecutePass(String name) { } default void afterExecutePass(String name) { } } @Environment(EnvType.CLIENT) static class InternalVirtualResource extends FrameGraphBuilder.VirtualResource { final int id; private final ResourceDescriptor descriptor; @Nullable private T physicalResource; public InternalVirtualResource(int id, String name, @Nullable FrameGraphBuilder.Pass createdBy, ResourceDescriptor descriptor) { super(name, createdBy); this.id = id; this.descriptor = descriptor; } @Override public T get() { return (T)Objects.requireNonNull(this.physicalResource, "Resource is not currently available"); } public void acquire(GraphicsResourceAllocator allocator) { if (this.physicalResource != null) { throw new IllegalStateException("Tried to acquire physical resource, but it was already assigned"); } else { this.physicalResource = allocator.acquire(this.descriptor); } } public void release(GraphicsResourceAllocator allocator) { if (this.physicalResource == null) { throw new IllegalStateException("Tried to release physical resource that was not allocated"); } else { allocator.release(this.descriptor, this.physicalResource); this.physicalResource = null; } } } @Environment(EnvType.CLIENT) class Pass implements FramePass { final int id; final String name; final List> writesFrom = new ArrayList(); final BitSet requiredResourceIds = new BitSet(); final BitSet requiredPassIds = new BitSet(); Runnable task = () -> {}; final List> resourcesToAcquire = new ArrayList(); final BitSet resourcesToRelease = new BitSet(); boolean disableCulling; public Pass(final int id, final String name) { this.id = id; this.name = name; } private void markResourceRequired(FrameGraphBuilder.Handle handle) { if (handle.holder instanceof FrameGraphBuilder.InternalVirtualResource internalVirtualResource) { this.requiredResourceIds.set(internalVirtualResource.id); } } private void markPassRequired(FrameGraphBuilder.Pass pass) { this.requiredPassIds.set(pass.id); } @Override public ResourceHandle createsInternal(String name, ResourceDescriptor descriptor) { FrameGraphBuilder.InternalVirtualResource internalVirtualResource = FrameGraphBuilder.this.createInternalResource(name, descriptor, this); this.requiredResourceIds.set(internalVirtualResource.id); return internalVirtualResource.handle; } @Override public void reads(ResourceHandle handle) { this._reads((FrameGraphBuilder.Handle)handle); } private void _reads(FrameGraphBuilder.Handle handle) { this.markResourceRequired(handle); if (handle.createdBy != null) { this.markPassRequired(handle.createdBy); } handle.readBy.set(this.id); } @Override public ResourceHandle readsAndWrites(ResourceHandle handle) { return this._readsAndWrites((FrameGraphBuilder.Handle)handle); } @Override public void requires(FramePass pass) { this.requiredPassIds.set(((FrameGraphBuilder.Pass)pass).id); } @Override public void disableCulling() { this.disableCulling = true; } private FrameGraphBuilder.Handle _readsAndWrites(FrameGraphBuilder.Handle handle) { this.writesFrom.add(handle); this._reads(handle); return handle.writeAndAlias(this); } @Override public void executes(Runnable task) { this.task = task; } public String toString() { return this.name; } } @Environment(EnvType.CLIENT) abstract static class VirtualResource { public final String name; public FrameGraphBuilder.Handle handle; public VirtualResource(String name, @Nullable FrameGraphBuilder.Pass createdBy) { this.name = name; this.handle = new FrameGraphBuilder.Handle<>(this, 0, createdBy); } public abstract T get(); public String toString() { return this.name; } } }