From dbde835b79709fd754fda24fe3c5f1e000dca5ca Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:12:07 -0400 Subject: [PATCH] made render object map threadsafe --- .../jme3/renderer/framegraph/FrameGraph.java | 170 ++++------- .../jme3/renderer/framegraph/PassIndex.java | 92 ++++++ .../framegraph/PassQueueExecutor.java | 244 ++++++++++++++++ .../renderer/framegraph/RenderObject.java | 56 +++- .../renderer/framegraph/RenderObjectMap.java | 267 ++++++++++++------ .../renderer/framegraph/RenderResource.java | 10 +- .../renderer/framegraph/ResourceList.java | 59 +--- .../renderer/framegraph/ResourceProducer.java | 2 +- .../framegraph/SavablePassConnection.java | 2 +- .../jme3/renderer/framegraph/TimeFrame.java | 60 ++-- .../framegraph/debug/GraphEventCapture.java | 2 +- .../framegraph/passes/RenderPass.java | 15 +- 12 files changed, 696 insertions(+), 283 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java create mode 100644 jme3-core/src/main/java/com/jme3/renderer/framegraph/PassQueueExecutor.java diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java index 00a70f993a..6928ed2970 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraph.java @@ -44,6 +44,7 @@ import com.jme3.renderer.framegraph.client.GraphSetting; import com.jme3.renderer.framegraph.debug.GraphEventCapture; import com.jme3.renderer.framegraph.passes.Attribute; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -88,10 +89,15 @@ */ public class FrameGraph { + /** + * Index for the main render thread pass queue. + */ + public static final int RENDER_THREAD = 0; + private final AssetManager assetManager; private final ResourceList resources; private final FGRenderContext context; - private final LinkedList passes = new LinkedList<>(); + private final ArrayList queues = new ArrayList<>(1); private final HashMap settings = new HashMap<>(); private String name = "FrameGraph"; private boolean rendered = false; @@ -105,6 +111,7 @@ public FrameGraph(AssetManager assetManager) { this.assetManager = assetManager; this.resources = new ResourceList(); this.context = new FGRenderContext(this); + this.queues.add(new PassQueueExecutor(this, RENDER_THREAD)); } /** * Creates a new framegraph from the given data. @@ -147,22 +154,6 @@ public void configure(RenderManager rm, ViewPort vp, AppProfiler prof, float tpf resources.setRenderManager(rm); context.target(rm, vp, prof, tpf); } - /** - * Pre-frame operations. - */ - public void preFrame() { - for (RenderPass p : passes) { - p.preFrame(context); - } - } - /** - * Post-queue operations. - */ - public void postQueue() { - for (RenderPass p : passes) { - p.postQueue(context); - } - } /** * Executes this framegraph. *

@@ -188,51 +179,50 @@ public boolean execute() { if (!rendered) { resources.beginRenderingSession(); } - for (RenderPass p : passes) { - if (prof != null) prof.fgStep(FgStep.Prepare, p.getProfilerName()); - if (cap != null) cap.prepareRenderPass(p.getIndex(), p.getProfilerName()); - p.prepareRender(context); + for (PassQueueExecutor queue : queues) { + for (RenderPass p : queue) { + if (prof != null) prof.fgStep(FgStep.Prepare, p.getProfilerName()); + if (cap != null) cap.prepareRenderPass(p.getIndex(), p.getProfilerName()); + p.prepareRender(context); + } } // cull passes and resources if (prof != null) prof.vpStep(VpStep.FrameGraphCull, vp, null); - for (RenderPass p : passes) { - p.countReferences(); + for (PassQueueExecutor queue : queues) { + for (RenderPass p : queue) { + p.countReferences(); + } } resources.cullUnreferenced(); // execute if (prof != null) prof.vpStep(VpStep.FrameGraphExecute, vp, null); context.pushRenderSettings(); - for (RenderPass p : passes) { - if (p.isUsed()) { - if (prof != null) prof.fgStep(FgStep.Execute, p.getProfilerName()); - if (cap != null) cap.executeRenderPass(p.getIndex(), p.getProfilerName()); - p.executeRender(context); - context.popRenderSettings(); - } + for (PassQueueExecutor p : queues) { + p.execute(context); } context.popFrameBuffer(); // reset if (prof != null) prof.vpStep(VpStep.FrameGraphReset, vp, null); - for (RenderPass p : passes) { - if (prof != null) prof.fgStep(FgStep.Reset, p.getProfilerName()); - p.resetRender(context); + for (PassQueueExecutor queue : queues) { + for (RenderPass p : queue) { + if (prof != null) prof.fgStep(FgStep.Reset, p.getProfilerName()); + p.resetRender(context); + } } // cleanup resources resources.clear(); - if (rendered) { - return false; - } else { - rendered = true; - return true; - } + if (rendered) return false; + else return (rendered = true); } /** * Should be called only when all rendering for the frame is complete. */ public void renderingComplete() { // notify passes - for (RenderPass p : passes) { - p.renderingComplete(); + for (PassQueueExecutor queue : queues) { + for (RenderPass p : queue) { + p.renderingComplete(); + } } // reset flags rendered = false; @@ -246,9 +236,7 @@ public void renderingComplete() { * @return given pass */ public T add(T pass) { - passes.addLast(pass); - pass.initializePass(this, passes.size()-1); - return pass; + return queues.get(RENDER_THREAD).add(pass); } /** * Adds the pass at the index in the pass queue. @@ -263,18 +251,7 @@ public T add(T pass) { * @return */ public T add(T pass, int index) { - if (index < 0) { - throw new IndexOutOfBoundsException("Index cannot be negative."); - } - if (index >= passes.size()) { - return add(pass); - } - passes.add(index, pass); - pass.initializePass(this, index); - for (RenderPass p : passes) { - p.shiftExecutionIndex(index, true); - } - return pass; + return queues.get(RENDER_THREAD).add(pass, index); } /** * Creates and adds an Attribute pass and links it to the given ticket. @@ -286,9 +263,7 @@ public T add(T pass, int index) { * @return created Attribute */ public Attribute addAttribute(ResourceTicket ticket) { - Attribute attr = add(new Attribute<>()); - attr.getInput(Attribute.INPUT).setSource(ticket); - return attr; + return queues.get(RENDER_THREAD).addAttribute(ticket); } /** @@ -299,9 +274,10 @@ public Attribute addAttribute(ResourceTicket ticket) { * @return first qualifying pass, or null */ public T get(Class type) { - for (RenderPass p : passes) { - if (type.isAssignableFrom(p.getClass())) { - return (T)p; + for (PassQueueExecutor q : queues) { + T p = q.get(type); + if (p != null) { + return p; } } return null; @@ -315,9 +291,10 @@ public T get(Class type) { * @return first qualifying pass, or null */ public T get(Class type, String name) { - for (RenderPass p : passes) { - if (name.equals(p.getName()) && type.isAssignableFrom(p.getClass())) { - return (T)p; + for (PassQueueExecutor q : queues) { + T p = q.get(type, name); + if (p != null) { + return p; } } return null; @@ -331,9 +308,10 @@ public T get(Class type, String name) { * @return pass of the id, or null */ public T get(Class type, int id) { - for (RenderPass p : passes) { - if (id == p.getId() && type.isAssignableFrom(p.getClass())) { - return (T)p; + for (PassQueueExecutor q : queues) { + T p = q.get(type, id); + if (p != null) { + return p; } } return null; @@ -349,25 +327,7 @@ public T get(Class type, int id) { * @throws IndexOutOfBoundsException if the index is less than zero or >= the queue size */ public RenderPass remove(int i) { - if (i < 0 || i >= passes.size()) { - throw new IndexOutOfBoundsException("Index "+i+" is out of bounds for size "+passes.size()); - } - int j = 0; - RenderPass removed = null; - for (Iterator it = passes.iterator(); it.hasNext();) { - RenderPass p = it.next(); - if (removed != null) { - p.disconnectInputsFrom(removed); - p.shiftExecutionIndex(i, false); - } else if (j++ == i) { - removed = p; - it.remove(); - } - } - if (removed != null) { - removed.cleanupPass(this); - } - return removed; + return queues.get(RENDER_THREAD).remove(i); } /** * Removes the given pass from the queue. @@ -378,25 +338,10 @@ public RenderPass remove(int i) { * @return true if the pass was removed from the queue */ public boolean remove(RenderPass pass) { - int i = 0; - boolean found = false; - for (Iterator it = passes.iterator(); it.hasNext();) { - RenderPass p = it.next(); - if (found) { - // shift execution indices down - p.disconnectInputsFrom(pass); - p.shiftExecutionIndex(i, false); - continue; - } - if (p == pass) { - it.remove(); - found = true; + for (PassQueueExecutor queue : queues) { + if (queue.remove(pass)) { + return true; } - i++; - } - if (found) { - pass.cleanupPass(this); - return true; } return false; } @@ -404,10 +349,9 @@ public boolean remove(RenderPass pass) { * Clears all passes from the pass queue. */ public void clear() { - for (RenderPass p : passes) { - p.cleanupPass(this); + for (PassQueueExecutor queue : queues) { + queue.clear(); } - passes.clear(); } /** @@ -568,6 +512,14 @@ public Context getCLContext() { public String getName() { return name; } + /** + * Returns true if this framegraph is running asynchronous passes. + * + * @return + */ + public boolean isAsync() { + return queues.size() > 1; + } /** * Applies the framegraph data to this framegraph. @@ -622,7 +574,7 @@ public FrameGraph loadData(String assetPath) { * @return */ public FrameGraphData createData() { - return new FrameGraphData(this, passes, settings); + return new FrameGraphData(this, queues, settings); } @Override diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java new file mode 100644 index 0000000000..5b9bff668c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java @@ -0,0 +1,92 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph; + +/** + * + * @author codex + */ +public class PassIndex { + + private int threadIndex, queueIndex; + + public PassIndex(int queueIndex) { + this(FrameGraph.RENDER_THREAD, queueIndex); + } + public PassIndex(int threadIndex, int queueIndex) { + this.threadIndex = threadIndex; + this.queueIndex = queueIndex; + } + + public PassIndex set(PassIndex index) { + threadIndex = index.threadIndex; + queueIndex = index.queueIndex; + return this; + } + + /** + * Shifts the thread index down one if the thread index is greater + * than the given index. + * + * @param i + * @return new thread index + */ + public int shiftThread(int i) { + if (threadIndex > i) { + threadIndex--; + } + return threadIndex; + } + /** + * Shifts the queue index down one if the thread index is greater than + * the given index. + * + * @param i + * @return new queue index + */ + public int shiftQueue(int i) { + if (queueIndex > i) { + queueIndex--; + } + return queueIndex; + } + + public int getThreadIndex() { + return threadIndex; + } + public int getQueueIndex() { + return queueIndex; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 19 * hash + this.threadIndex; + hash = 19 * hash + this.queueIndex; + return hash; + } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PassIndex other = (PassIndex) obj; + if (this.threadIndex != other.threadIndex) { + return false; + } + return this.queueIndex == other.queueIndex; + } + @Override + public String toString() { + return PassIndex.class.getSimpleName()+"[thread="+threadIndex+", queue="+queueIndex+']'; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassQueueExecutor.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassQueueExecutor.java new file mode 100644 index 0000000000..fc327c5253 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassQueueExecutor.java @@ -0,0 +1,244 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.framegraph; + +import com.jme3.profile.FgStep; +import static com.jme3.renderer.framegraph.FrameGraph.RENDER_THREAD; +import com.jme3.renderer.framegraph.passes.Attribute; +import com.jme3.renderer.framegraph.passes.RenderPass; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * + * @author codex + */ +public class PassQueueExecutor implements Runnable, Iterable { + + private final FrameGraph frameGraph; + private final LinkedList queue = new LinkedList<>(); + private int index; + private Thread thread; + private FGRenderContext context; + + public PassQueueExecutor(FrameGraph frameGraph, int index) { + this.frameGraph = frameGraph; + this.index = index; + } + + @Override + public void run() { + boolean async = isAsync(); + for (RenderPass p : queue) { + if (p.isUsed()) { + /*if (!async) { + if (context.isProfilerAvailable()) context.getProfiler().fgStep(FgStep.Execute, p.getProfilerName()); + if (cap != null) cap.executeRenderPass(p.getIndex(), p.getProfilerName()); + }*/ + p.executeRender(context); + if (!async) { + context.popRenderSettings(); + } + } + } + } + @Override + public Iterator iterator() { + return queue.iterator(); + } + + public void execute(FGRenderContext context) { + if (!isAsync()) { + run(); + } else { + thread = new Thread(this); + thread.start(); + } + } + + /** + * Adds the pass to end of the pass queue. + * + * @param + * @param pass + * @return given pass + */ + public T add(T pass) { + queue.addLast(pass); + pass.initializePass(frameGraph, new PassIndex(index, queue.size()-1)); + return pass; + } + /** + * Adds the pass at the index in the pass queue. + *

+ * If the index is >= the current queue size, the pass will + * be added to the end of the queue. Passes above the added pass + * will have their indexes shifted. + * + * @param + * @param pass + * @param index + * @return + */ + public T add(T pass, int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index cannot be negative."); + } + if (index >= queue.size()) { + return add(pass); + } + queue.add(index, pass); + pass.initializePass(frameGraph, new PassIndex(this.index, index)); + for (RenderPass p : queue) { + p.shiftExecutionIndex(index, true); + } + return pass; + } + /** + * Creates and adds an Attribute pass and links it to the given ticket. + *

+ * This is handy for quickly debugging various resources in the graph. + * + * @param + * @param ticket ticket to reference from + * @return created Attribute + */ + public Attribute addAttribute(ResourceTicket ticket) { + Attribute attr = add(new Attribute<>()); + attr.getInput(Attribute.INPUT).setSource(ticket); + return attr; + } + + /** + * Gets the first pass that is of or a subclass of the given class. + * + * @param + * @param type + * @return first qualifying pass, or null + */ + public T get(Class type) { + for (RenderPass p : queue) { + if (type.isAssignableFrom(p.getClass())) { + return (T)p; + } + } + return null; + } + /** + * Gets the first pass of the given class that is named as given. + * + * @param + * @param type + * @param name + * @return first qualifying pass, or null + */ + public T get(Class type, String name) { + for (RenderPass p : queue) { + if (name.equals(p.getName()) && type.isAssignableFrom(p.getClass())) { + return (T)p; + } + } + return null; + } + /** + * Gets the pass that holds the given id number. + * + * @param + * @param type + * @param id + * @return pass of the id, or null + */ + public T get(Class type, int id) { + for (RenderPass p : queue) { + if (id == p.getId() && type.isAssignableFrom(p.getClass())) { + return (T)p; + } + } + return null; + } + + /** + * Removes the pass at the index in the queue. + *

+ * Passes above the removed pass will have their indexes shifted. + * + * @param i + * @return removed pass + * @throws IndexOutOfBoundsException if the index is less than zero or >= the queue size + */ + public RenderPass remove(int i) { + if (i < 0 || i >= queue.size()) { + throw new IndexOutOfBoundsException("Index "+i+" is out of bounds for size "+queue.size()); + } + int j = 0; + RenderPass removed = null; + for (Iterator it = queue.iterator(); it.hasNext();) { + RenderPass p = it.next(); + if (removed != null) { + p.disconnectInputsFrom(removed); + p.shiftExecutionIndex(i, false); + } else if (j++ == i) { + removed = p; + it.remove(); + } + } + if (removed != null) { + removed.cleanupPass(frameGraph); + } + return removed; + } + /** + * Removes the given pass from the queue. + *

+ * Passes above the removed pass will have their indexes shifted. + * + * @param pass + * @return true if the pass was removed from the queue + */ + public boolean remove(RenderPass pass) { + int i = 0; + boolean found = false; + for (Iterator it = queue.iterator(); it.hasNext();) { + RenderPass p = it.next(); + if (found) { + // shift execution indices down + p.disconnectInputsFrom(pass); + p.shiftExecutionIndex(i, false); + continue; + } + if (p == pass) { + it.remove(); + found = true; + } + i++; + } + if (found) { + pass.cleanupPass(frameGraph); + return true; + } + return false; + } + /** + * Clears all passes from the pass queue. + */ + public void clear() { + for (RenderPass p : queue) { + p.cleanupPass(frameGraph); + } + queue.clear(); + } + + public int shiftIndex(int i) { + if (index > i) { + index--; + } + return index; + } + + public boolean isAsync() { + return index != FrameGraph.RENDER_THREAD; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java index f6978038e3..c317152abf 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObject.java @@ -33,7 +33,8 @@ import com.jme3.renderer.framegraph.definitions.ResourceDef; import com.jme3.util.NativeObject; -import java.util.BitSet; +import java.util.Iterator; +import java.util.LinkedList; import java.util.function.Consumer; /** @@ -51,11 +52,13 @@ public class RenderObject { private final long id; private final T object; - private final BitSet reservations = new BitSet(); + private final LinkedList reservations = new LinkedList<>(); private int timeoutDuration; private int timeout = 0; private boolean acquired = false; private boolean constant = false; + private boolean inspect = false; + private boolean prioritized = false; private Consumer disposer; /** @@ -83,6 +86,23 @@ else if (object instanceof NativeObject) { } } + public void startInspect() { + inspect = true; + } + public void endInspect() { + inspect = false; + } + public boolean isInspect() { + return inspect; + } + + public void setPrioritized(boolean prioritized) { + this.prioritized = prioritized; + } + public boolean isPrioritized() { + return prioritized; + } + /** * Acquires this render object for use. */ @@ -107,22 +127,40 @@ public void release() { * * @param index */ - public void reserve(int index) { - reservations.set(index); + public void reserve(PassIndex index) { + reservations.add(index); } /** * Disposes the internal object. */ public void dispose() { + // ensure this cannot be acquired + acquired = true; disposer.accept(object); } + /** + * Returns true if + * + * @param index + * @return + */ + public boolean claimReservation(PassIndex index) { + for (Iterator it = reservations.iterator(); it.hasNext();) { + if (it.next().equals(index)) { + it.remove(); + return true; + } + } + return false; + } /** * Clears all reservations. */ public void clearReservations() { reservations.clear(); } + /** * Ticks down the timer tracking frames since last use. * @@ -172,8 +210,8 @@ public boolean isAcquired() { * @param index * @return */ - public boolean isReservedAt(int index) { - return reservations.get(index); + public boolean isReservedAt(PassIndex index) { + return reservations.contains(index); } /** * Returns true if this render object is reserved within the time frame. @@ -182,11 +220,11 @@ public boolean isReservedAt(int index) { * @return */ public boolean isReservedWithin(TimeFrame frame) { - if (frame.getStartIndex() >= reservations.size()) { + if (frame.getStartQueueIndex() >= reservations.size()) { return false; } - int n = Math.min(reservations.size()-1, frame.getEndIndex()); - for (int i = frame.getStartIndex(); i <= n; i++) { + int n = Math.min(reservations.size()-1, frame.getEndQueueIndex()); + for (int i = frame.getStartQueueIndex(); i <= n; i++) { if (reservations.get(i)) { return true; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java index 107c36796c..ad8eb2c6be 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderObjectMap.java @@ -36,6 +36,9 @@ import com.jme3.renderer.framegraph.definitions.ResourceDef; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Manages creation, reallocation, and disposal of {@link RenderObject}s. @@ -45,7 +48,8 @@ public class RenderObjectMap { private final RenderManager renderManager; - private final HashMap objectMap = new HashMap<>(); + private final Map objectMap; + private final boolean async; private int staticTimeout = 1; // statistics @@ -58,8 +62,14 @@ public class RenderObjectMap { private int totalObjects = 0; private int flushedObjects = 0; - public RenderObjectMap(RenderManager renderManager) { + public RenderObjectMap(RenderManager renderManager, boolean async) { this.renderManager = renderManager; + this.async = async; + if (this.async) { + objectMap = new ConcurrentHashMap<>(); + } else { + objectMap = new HashMap<>(); + } } /** @@ -99,14 +109,24 @@ protected boolean isAvailable(RenderObject object) { * Allocates a render object to the resource. *

* First, if this resource holds an object id, the corresponding render object, - * if it still exists, will be tried for reallocation. Then, each render object - * will be tried for reallocation. Finally, if all else fails, a new render object + * if it still exists, will be tried for reallocation. If that fails, each render object + * will be tried for reallocation. Finally, if that fails, a new render object * will be created and allocated to the resource. + *

+ * If this RenderObjectMap is asynchronous, this method is threadsafe. * * @param * @param resource */ public void allocate(RenderResource resource) { + if (async) { + allocateAsync(resource); + } else { + allocateSync(resource); + } + } + + private void allocateSync(RenderResource resource) { if (resource.isUndefined()) { throw new IllegalArgumentException("Cannot allocate object to an undefined resource."); } @@ -114,7 +134,8 @@ public void allocate(RenderResource resource) { totalAllocations++; ResourceDef def = resource.getDefinition(); if (def.isUseExisting()) { - if (allocateSpecific(resource)) { + // first try allocating a specific object, which is much faster + if (allocateSpecificSync(resource)) { return; } // find object to allocate @@ -122,15 +143,18 @@ public void allocate(RenderResource resource) { RenderObject indirectObj = null; for (RenderObject obj : objectMap.values()) { if (isAvailable(obj) && !obj.isReservedWithin(resource.getLifeTime())) { + // try applying a direct resource T r = def.applyDirectResource(obj.getObject()); if (r != null) { resource.setObject(obj, r); if (cap != null) cap.reallocateObject(obj.getId(), resource.getIndex(), resource.getResource().getClass().getSimpleName()); objectsReallocated++; + obj.endInspect(); return; } - if (indirectObj == null) { + // then try applying an indirect resource, which is not as desirable + if (!obj.isPrioritized() && indirectObj == null) { indirectRes = def.applyIndirectResource(obj.getObject()); if (indirectRes != null) { indirectObj = obj; @@ -138,12 +162,14 @@ public void allocate(RenderResource resource) { } } } + // allocate indirect object if (indirectObj != null) { - // allocate indirect object + indirectObj.startInspect(); resource.setObject(indirectObj, indirectRes); if (cap != null) cap.reallocateObject(indirectObj.getId(), resource.getIndex(), resource.getResource().getClass().getSimpleName()); objectsReallocated++; + indirectObj.endInspect(); return; } } @@ -153,62 +179,164 @@ public void allocate(RenderResource resource) { resource.getIndex(), resource.getResource().getClass().getSimpleName()); objectsCreated++; } - /** - * Allocates the specific render object directly referenced by the resource, - * if one is referenced. - * - * @param - * @param resource - * @return true if the specific render object was allocated - */ - public boolean allocateSpecific(RenderResource resource) { + private boolean allocateSpecificSync(RenderResource resource) { GraphEventCapture cap = renderManager.getGraphCapture(); ResourceDef def = resource.getDefinition(); long id = resource.getTicket().getObjectId(); if (id < 0) return false; // allocate reserved object - RenderObject obj = objectMap.get(id); - if (cap != null) cap.attemptReallocation(id, resource.getIndex()); - if (obj != null && isAvailable(obj) - && (obj.isReservedAt(resource.getLifeTime().getStartIndex()) - || !obj.isReservedWithin(resource.getLifeTime()))) { - // reserved object is only applied if it is accepted by the definition - T r = def.applyDirectResource(obj.getObject()); - if (r == null) { - r = def.applyIndirectResource(obj.getObject()); - } - if (r != null) { - resource.setObject(obj, r); - if (cap != null) cap.reallocateObject(id, resource.getIndex(), - resource.getResource().getClass().getSimpleName()); - completedReservations++; - objectsReallocated++; - return true; + RenderObject obj = objectMap.get(id); + if (obj != null) { + if (cap != null) cap.attemptReallocation(id, resource.getIndex()); + if (isAvailable(obj) && (obj.isReservedAt(resource.getLifeTime().getStartQueueIndex()) + || !obj.isReservedWithin(resource.getLifeTime()))) { + // reserved object is only applied if it is accepted by the definition + T r = def.applyDirectResource(obj.getObject()); + if (r == null) { + r = def.applyIndirectResource(obj.getObject()); + } + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(id, resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + completedReservations++; + objectsReallocated++; + return true; + } } + if (cap != null) cap.allocateSpecificFailed(obj, resource); } failedReservations++; - if (cap != null) cap.allocateSpecificFailed(obj, resource); return false; } - /** - * Directly creates a new render object containing the value for the render - * resource. - *

- * The object is still subject to the resource's definition, although the definition - * may not have created the internal value. - * - * @param - * @param resource - * @param value - */ - public void allocateDirect(RenderResource resource, T value) { - RenderObject object = create(resource.getDefinition(), value); - resource.setObject(object, value); - if (renderManager.getGraphCapture() != null) { - renderManager.getGraphCapture().setObjectDirect( - object.getId(), resource.getIndex(), value.getClass().getSimpleName()); + private void allocateAsync(RenderResource resource) { + if (resource.isUndefined()) { + throw new IllegalArgumentException("Cannot allocate object to an undefined resource."); + } + GraphEventCapture cap = renderManager.getGraphCapture(); + totalAllocations++; + ResourceDef def = resource.getDefinition(); + if (def.isUseExisting()) { + // first try allocating a specific object, which is much faster + if (allocateSpecificAsync(resource)) { + return; + } + // find object to allocate + T indirectRes = null; + RenderObject indirectObj = null; + LinkedList skipped = new LinkedList<>(); + Iterator it = objectMap.values().iterator(); + boolean next; + while ((next = it.hasNext()) || !skipped.isEmpty()) { + RenderObject obj; + if (next) obj = it.next(); + else obj = skipped.removeFirst(); + if (isAvailable(obj)) { + if ((next || !skipped.isEmpty()) && obj.isInspect()) { + // Inspect this object later, because something else is inspecting it. + // This makes this thread try other objects first, instead of waiting + // for a synchronized block to be available. + skipped.addLast(obj); + continue; + } + // If multiple threads do happen to be here at the same time, ensure only one + // will inspect at a time. + synchronized (obj) { + // The thread we were waiting on may have claimed to object, so check again + // if it is available. + if (!isAvailable(obj)) { + continue; + } + obj.startInspect(); + if (!obj.isReservedWithin(resource.getLifeTime())) { + // try applying a direct resource + T r = def.applyDirectResource(obj.getObject()); + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(obj.getId(), resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + objectsReallocated++; + obj.endInspect(); + return; + } + // then try applying an indirect resource, which is not as desirable + if (!obj.isPrioritized() && indirectObj == null) { + indirectRes = def.applyIndirectResource(obj.getObject()); + if (indirectRes != null) { + indirectObj = obj; + // make sure no other thread attempts to apply this indirectly at the same time + obj.setPrioritized(true); + obj.endInspect(); + continue; + } + } + } + obj.endInspect(); + } + } + } + // allocate indirect object + if (indirectObj != null) synchronized (indirectObj) { + // disable priority flag + indirectObj.setPrioritized(false); + // check again if object is available + if (isAvailable(indirectObj)) { + indirectObj.startInspect(); + resource.setObject(indirectObj, indirectRes); + if (cap != null) cap.reallocateObject(indirectObj.getId(), resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + objectsReallocated++; + indirectObj.endInspect(); + } else { + // In the unlikely event that another thread "steals" this object + // from this thread, try allocating again. + allocateAsync(resource); + } + return; + } } + // create new object + resource.setObject(create(def)); + if (cap != null) cap.createObject(resource.getObject().getId(), + resource.getIndex(), resource.getResource().getClass().getSimpleName()); + objectsCreated++; + } + private boolean allocateSpecificAsync(RenderResource resource) { + GraphEventCapture cap = renderManager.getGraphCapture(); + ResourceDef def = resource.getDefinition(); + long id = resource.getTicket().getObjectId(); + if (id < 0) return false; + // allocate reserved object + RenderObject obj = objectMap.get(id); + if (obj != null) { + if (cap != null) cap.attemptReallocation(id, resource.getIndex()); + if (isAvailable(obj)) synchronized (obj) { + obj.startInspect(); + if ((obj.isReservedAt(resource.getLifeTime().getStartQueueIndex()) + || !obj.isReservedWithin(resource.getLifeTime()))) { + // reserved object is only applied if it is accepted by the definition + T r = def.applyDirectResource(obj.getObject()); + if (r == null) { + r = def.applyIndirectResource(obj.getObject()); + } + if (r != null) { + resource.setObject(obj, r); + if (cap != null) cap.reallocateObject(id, resource.getIndex(), + resource.getResource().getClass().getSimpleName()); + completedReservations++; + objectsReallocated++; + obj.endInspect(); + return true; + } + } + obj.endInspect(); + } + if (cap != null) cap.allocateSpecificFailed(obj, resource); + } + failedReservations++; + return false; } + /** * Makes a reservation of render object holding the specified id at the render * pass index so that no other resource may (without a reservation) use that @@ -218,7 +346,7 @@ public void allocateDirect(RenderResource resource, T value) { * @param index * @return */ - public boolean reserve(long objectId, int index) { + public boolean reserve(long objectId, PassIndex index) { RenderObject obj = objectMap.get(objectId); if (obj != null) { obj.reserve(index); @@ -230,39 +358,6 @@ public boolean reserve(long objectId, int index) { } return false; } - /** - * Untracks the render object held by the resource. - *

- * If the resource is virtual, a new resource will be allocated then - * immediately untracked. - * - * @param - * @param resource - * @return - */ - public T extract(RenderResource resource) { - if (resource.isUndefined()) { - return null; - } - if (resource.isVirtual()) { - allocate(resource); - } - RenderObject obj = objectMap.remove(resource.getTicket().getObjectId()); - return (obj != null ? obj.getObject() : null); - } - /** - * Removes the render object holding the given value from the map. - * - * @param value - */ - public void remove(Object value) { - for (Iterator it = objectMap.values().iterator(); it.hasNext();) { - RenderObject object = it.next(); - if (object.getObject() == value) { - it.remove(); - } - } - } /** * Disposes the render object pointed to by the resource. * @@ -273,10 +368,10 @@ public void dispose(RenderResource resource) { if (id >= 0) { RenderObject obj = objectMap.remove(id); if (obj != null) { + obj.dispose(); if (renderManager.getGraphCapture() != null) { renderManager.getGraphCapture().disposeObject(id); } - obj.dispose(); } } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderResource.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderResource.java index 5c64e6e811..e7dbacdbb1 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderResource.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/RenderResource.java @@ -42,10 +42,10 @@ */ public class RenderResource { - private final ResourceProducer producer; - private final ResourceDef def; - private final ResourceTicket ticket; - private final TimeFrame lifetime; + private ResourceProducer producer; + private ResourceDef def; + private ResourceTicket ticket; + private TimeFrame lifetime; private RenderObject object; private T resource; private int refs = 0; @@ -70,7 +70,7 @@ public RenderResource(ResourceProducer producer, ResourceDef def, ResourceTic * * @param index */ - public void reference(int index) { + public void reference(PassIndex index) { lifetime.extendTo(index); refs++; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java index 01e93309ba..26f6e1fe29 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceList.java @@ -52,6 +52,7 @@ public class ResourceList { private RenderObjectMap map; private GraphEventCapture cap; private ArrayList resources = new ArrayList<>(INITIAL_SIZE); + private LinkedList references = new LinkedList<>(); private int nextSlot = 0; private int textureBinds = 0; @@ -183,7 +184,7 @@ public ResourceTicket declare(ResourceProducer producer, ResourceDef d * @param passIndex * @param ticket */ - public void reserve(int passIndex, ResourceTicket ticket) { + public void reserve(PassIndex passIndex, ResourceTicket ticket) { if (ticket.getObjectId() >= 0) { map.reserve(ticket.getObjectId(), passIndex); ticket.copyObjectTo(locate(ticket).getTicket()); @@ -196,7 +197,7 @@ public void reserve(int passIndex, ResourceTicket ticket) { * @param passIndex * @param tickets */ - public void reserve(int passIndex, ResourceTicket... tickets) { + public void reserve(PassIndex passIndex, ResourceTicket... tickets) { for (ResourceTicket t : tickets) { reserve(passIndex, t); } @@ -212,7 +213,7 @@ public void reserve(int passIndex, ResourceTicket... tickets) { * @param passIndex render pass index * @param ticket */ - public void reference(int passIndex, ResourceTicket ticket) { + public void reference(PassIndex passIndex, ResourceTicket ticket) { RenderResource resource = locate(ticket); resource.reference(passIndex); if (cap != null) cap.referenceResource(resource.getIndex(), ticket.getName()); @@ -226,7 +227,7 @@ public void reference(int passIndex, ResourceTicket ticket) { * @param ticket * @return */ - public boolean referenceOptional(int passIndex, ResourceTicket ticket) { + public boolean referenceOptional(PassIndex passIndex, ResourceTicket ticket) { if (validate(ticket)) { reference(passIndex, ticket); return true; @@ -240,7 +241,7 @@ public boolean referenceOptional(int passIndex, ResourceTicket ticket) { * @param passIndex render pass index * @param tickets */ - public void reference(int passIndex, ResourceTicket... tickets) { + public void reference(PassIndex passIndex, ResourceTicket... tickets) { for (ResourceTicket t : tickets) { reference(passIndex, t); } @@ -252,7 +253,7 @@ public void reference(int passIndex, ResourceTicket... tickets) { * @param passIndex render pass index * @param tickets */ - public void referenceOptional(int passIndex, ResourceTicket... tickets) { + public void referenceOptional(PassIndex passIndex, ResourceTicket... tickets) { for (ResourceTicket t : tickets) { referenceOptional(passIndex, t); } @@ -543,52 +544,6 @@ public void setPrimitive(ResourceTicket ticket, T value) { locate(ticket).setPrimitive(value); } - protected T extract(RenderResource resource, ResourceTicket ticket) { - if (!resource.isUsed()) { - throw new IllegalStateException(resource+" was unexpectedly extracted."); - } - resource.getTicket().copyObjectTo(ticket); - return map.extract(resource); - } - - /** - * Permanently extracts the object from the object manager. - *

- * Extracted objects are no longer tracked by the object manager, - * and can therefore not be reallocated for any task. - * - * @param - * @param ticket - * @return - */ - public T extract(ResourceTicket ticket) { - RenderResource resource = locate(ticket); - T object = extract(resource, ticket); - if (object == null) { - throw new NullPointerException("Failed to extract resource."); - } - return object; - } - - /** - * If the ticket is not null and has a positive or zero world index, an object - * will be extracted by the resource and returned. - *

- * Otherwise, the given default value will be returned. - * - * @param - * @param ticket - * @param value - * @return - */ - public T extractOrElse(ResourceTicket ticket, T value) { - if (ticket != null && ticket.getWorldIndex() >= 0) { - T object = extract(locate(ticket), ticket); - if (object != null) return object; - } - return value; - } - /** * Releases the resource from use. * diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceProducer.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceProducer.java index d9ffb49938..7637a849c0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceProducer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/ResourceProducer.java @@ -45,7 +45,7 @@ public interface ResourceProducer { * * @return */ - public int getExecutionIndex(); + public PassIndex getExecutionIndex(); /** * Dereferences this producer. diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/SavablePassConnection.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/SavablePassConnection.java index ba6ec2b958..6ea12fb753 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/SavablePassConnection.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/SavablePassConnection.java @@ -39,7 +39,7 @@ import java.io.IOException; /** - * Represents an abstract connection between render passes. + * Represents an abstract connection between render passes that can be saved. * * @author codex */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java index 206b1a34bc..8238cc293b 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/TimeFrame.java @@ -34,12 +34,16 @@ /** * Represents a period of time starting at the start of the indexed pass, and * lasting for the duration of a number of following passes. + *

+ * An asynchronous timeframe indicates that the end is unpredictable. * * @author codex */ public class TimeFrame { - private int index, length; + private int thread; + private int queue, length; + private boolean async = false; /** * @@ -47,13 +51,14 @@ public class TimeFrame { private TimeFrame() {} /** * - * @param passIndex + * @param index * @param length */ - public TimeFrame(int passIndex, int length) { - this.index = passIndex; + public TimeFrame(PassIndex index, int length) { + this.thread = index.getThreadIndex(); + this.queue = index.getQueueIndex(); this.length = length; - if (this.index < 0) { + if (this.queue < 0) { throw new IllegalArgumentException("Pass index cannot be negative."); } if (this.length < 0) { @@ -62,13 +67,16 @@ public TimeFrame(int passIndex, int length) { } /** - * Extends, but does not retract, the length so that this time frame - * includes the given index. + * Extends the length so that this time frame includes the given index. * * @param passIndex */ - public void extendTo(int passIndex) { - length = Math.max(length, passIndex-this.index); + public void extendTo(PassIndex passIndex) { + if (passIndex.getThreadIndex() != thread) { + async = true; + } else { + length = Math.max(length, passIndex.getQueueIndex()-this.queue); + } } /** * Copies this to the target time frame. @@ -80,18 +88,28 @@ public TimeFrame copyTo(TimeFrame target) { if (target == null) { target = new TimeFrame(); } - target.index = index; + target.thread = thread; + target.queue = queue; target.length = length; + target.async = async; return target; } + /** + * Gets the index of the thread this timeframe is based from. + * + * @return + */ + public int getThreadIndex() { + return thread; + } /** * Gets index of the first pass this time frame includes. * * @return */ - public int getStartIndex() { - return index; + public int getStartQueueIndex() { + return queue; } /** * Gets the length. @@ -106,8 +124,18 @@ public int getLength() { * * @return */ - public int getEndIndex() { - return index+length; + public int getEndQueueIndex() { + return queue+length; + } + /** + * Returns true if this timeframe is asynchronous. + *

+ * An asynchronous timeframe's end index is unreliable. + * + * @return + */ + public boolean isAsync() { + return async; } /** @@ -117,7 +145,7 @@ public int getEndIndex() { * @return */ public boolean overlaps(TimeFrame time) { - return index <= time.index+time.length && index+length >= time.index; + return queue <= time.queue+time.length && queue+length >= time.queue; } /** * Returns true if this time frame includes the given index. @@ -126,7 +154,7 @@ public boolean overlaps(TimeFrame time) { * @return */ public boolean includes(int index) { - return index <= index && index+length >= index; + return queue <= index && queue+length >= index; } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java index c1de89125f..d4e0ee99fa 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/debug/GraphEventCapture.java @@ -103,7 +103,7 @@ public void allocateSpecificFailed(RenderObject object, RenderResource resource) new Check("nullObject", () -> object != null, true), new Check("acquired", () -> !object.isAcquired()), new Check("constant", () -> !object.isConstant()), - new Check("conflicting", () -> object.isReservedAt(resource.getLifeTime().getStartIndex()) + new Check("conflicting", () -> object.isReservedAt(resource.getLifeTime().getStartQueueIndex()) || !object.isReservedWithin(resource.getLifeTime())) )); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java index 08bcfacac3..2e112eaa20 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/RenderPass.java @@ -38,6 +38,7 @@ import com.jme3.export.Savable; import com.jme3.renderer.framegraph.FGRenderContext; import com.jme3.renderer.framegraph.FrameGraph; +import com.jme3.renderer.framegraph.PassIndex; import com.jme3.renderer.framegraph.ResourceList; import com.jme3.renderer.framegraph.ResourceProducer; import com.jme3.renderer.framegraph.ResourceTicket; @@ -66,7 +67,7 @@ public abstract class RenderPass implements ResourceProducer, Savable { private int exportId = -1; private String name; protected FrameGraph frameGraph; - private int index = -1; + private PassIndex index; private int refs = 0; private final LinkedList inputs = new LinkedList<>(); private final LinkedList outputs = new LinkedList<>(); @@ -81,7 +82,7 @@ public abstract class RenderPass implements ResourceProducer, Savable { * @param frameGraph * @param index execution index */ - public void initializePass(FrameGraph frameGraph, int index) { + public void initializePass(FrameGraph frameGraph, PassIndex index) { this.frameGraph = frameGraph; this.index = index; this.resources = frameGraph.getResources(); @@ -100,6 +101,14 @@ public void prepareRender(FGRenderContext context) { throw new IllegalStateException("Pass is not properly initialized for rendering."); } prepare(context); + } + /** + * + * @param context + * @return + */ + public boolean asyncWait(FGRenderContext context) { + } /** * Executes the pass. @@ -883,7 +892,7 @@ public int getNumGroups() { } @Override - public int getExecutionIndex() { + public PassIndex getExecutionIndex() { return index; } @Override