362 lines
12 KiB
Java
362 lines
12 KiB
Java
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<FrameGraphBuilder.InternalVirtualResource<?>> internalResources = new ArrayList();
|
|
private final List<FrameGraphBuilder.ExternalResource<?>> externalResources = new ArrayList();
|
|
private final List<FrameGraphBuilder.Pass> 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 <T> ResourceHandle<T> importExternal(String name, T resource) {
|
|
FrameGraphBuilder.ExternalResource<T> externalResource = new FrameGraphBuilder.ExternalResource<>(name, null, resource);
|
|
this.externalResources.add(externalResource);
|
|
return externalResource.handle;
|
|
}
|
|
|
|
public <T> ResourceHandle<T> createInternal(String name, ResourceDescriptor<T> descriptor) {
|
|
return this.createInternalResource(name, descriptor, null).handle;
|
|
}
|
|
|
|
<T> FrameGraphBuilder.InternalVirtualResource<T> createInternalResource(
|
|
String name, ResourceDescriptor<T> descriptor, @Nullable FrameGraphBuilder.Pass createdBy
|
|
) {
|
|
int i = this.internalResources.size();
|
|
FrameGraphBuilder.InternalVirtualResource<T> 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<FrameGraphBuilder.Pass> 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<FrameGraphBuilder.Pass> 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<FrameGraphBuilder.Pass> 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<FrameGraphBuilder.Pass> 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<FrameGraphBuilder.Pass> 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<T> extends FrameGraphBuilder.VirtualResource<T> {
|
|
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<T> implements ResourceHandle<T> {
|
|
final FrameGraphBuilder.VirtualResource<T> holder;
|
|
private final int version;
|
|
@Nullable
|
|
final FrameGraphBuilder.Pass createdBy;
|
|
final BitSet readBy = new BitSet();
|
|
@Nullable
|
|
private FrameGraphBuilder.Handle<T> aliasedBy;
|
|
|
|
Handle(FrameGraphBuilder.VirtualResource<T> 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<T> 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<T> 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<T> extends FrameGraphBuilder.VirtualResource<T> {
|
|
final int id;
|
|
private final ResourceDescriptor<T> descriptor;
|
|
@Nullable
|
|
private T physicalResource;
|
|
|
|
public InternalVirtualResource(int id, String name, @Nullable FrameGraphBuilder.Pass createdBy, ResourceDescriptor<T> 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<FrameGraphBuilder.Handle<?>> writesFrom = new ArrayList();
|
|
final BitSet requiredResourceIds = new BitSet();
|
|
final BitSet requiredPassIds = new BitSet();
|
|
Runnable task = () -> {};
|
|
final List<FrameGraphBuilder.InternalVirtualResource<?>> 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 <T> void markResourceRequired(FrameGraphBuilder.Handle<T> 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 <T> ResourceHandle<T> createsInternal(String name, ResourceDescriptor<T> descriptor) {
|
|
FrameGraphBuilder.InternalVirtualResource<T> internalVirtualResource = FrameGraphBuilder.this.createInternalResource(name, descriptor, this);
|
|
this.requiredResourceIds.set(internalVirtualResource.id);
|
|
return internalVirtualResource.handle;
|
|
}
|
|
|
|
@Override
|
|
public <T> void reads(ResourceHandle<T> handle) {
|
|
this._reads((FrameGraphBuilder.Handle<T>)handle);
|
|
}
|
|
|
|
private <T> void _reads(FrameGraphBuilder.Handle<T> handle) {
|
|
this.markResourceRequired(handle);
|
|
if (handle.createdBy != null) {
|
|
this.markPassRequired(handle.createdBy);
|
|
}
|
|
|
|
handle.readBy.set(this.id);
|
|
}
|
|
|
|
@Override
|
|
public <T> ResourceHandle<T> readsAndWrites(ResourceHandle<T> handle) {
|
|
return this._readsAndWrites((FrameGraphBuilder.Handle<T>)handle);
|
|
}
|
|
|
|
@Override
|
|
public void requires(FramePass pass) {
|
|
this.requiredPassIds.set(((FrameGraphBuilder.Pass)pass).id);
|
|
}
|
|
|
|
@Override
|
|
public void disableCulling() {
|
|
this.disableCulling = true;
|
|
}
|
|
|
|
private <T> FrameGraphBuilder.Handle<T> _readsAndWrites(FrameGraphBuilder.Handle<T> 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<T> {
|
|
public final String name;
|
|
public FrameGraphBuilder.Handle<T> 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;
|
|
}
|
|
}
|
|
}
|