From a3eee4c7c89426161f8cce1508a5c730169d10d6 Mon Sep 17 00:00:00 2001 From: codex <103840984+codex128@users.noreply.github.com> Date: Wed, 26 Jun 2024 18:16:51 -0400 Subject: [PATCH] added multithreading --- .../java/com/jme3/renderer/RenderManager.java | 2 +- .../com/jme3/renderer/framegraph/Access.java | 53 ++++++++ .../renderer/framegraph/FGRenderContext.java | 2 +- .../jme3/renderer/framegraph/FrameGraph.java | 56 ++++++-- .../renderer/framegraph/FrameGraphData.java | 69 +++++----- .../framegraph/FrameGraphFactory.java | 10 +- .../jme3/renderer/framegraph/PassIndex.java | 14 +- .../framegraph/PassQueueExecutor.java | 127 +++++++++++++++--- .../renderer/framegraph/RenderObject.java | 73 +++++----- .../renderer/framegraph/RenderObjectMap.java | 35 +---- .../renderer/framegraph/RenderResource.java | 24 +++- .../renderer/framegraph/ResourceList.java | 104 +++++++------- .../framegraph/debug/GraphEventCapture.java | 16 +-- .../framegraph/passes/DeferredPass.java | 3 + .../framegraph/passes/GBufferPass.java | 13 -- .../framegraph/passes/RenderPass.java | 22 +-- .../framegraph/passes/SceneEnqueuePass.java | 25 +++- .../TestSimpleDeferredLighting.java | 2 +- 18 files changed, 413 insertions(+), 237 deletions(-) create mode 100644 jme3-core/src/main/java/com/jme3/renderer/framegraph/Access.java diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index d22cbdf47e..1023798171 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -127,7 +127,7 @@ public class RenderManager { public RenderManager(Renderer renderer) { this.renderer = renderer; this.forcedOverrides.add(boundDrawBufferId); - this.renderObjects = new RenderObjectMap(this); + this.renderObjects = new RenderObjectMap(this, true); } /** diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/Access.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/Access.java new file mode 100644 index 0000000000..3690e42b99 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/Access.java @@ -0,0 +1,53 @@ +/* + * 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 enum Access { + + /** + * Indicates that the resource is accessed for reading only. + */ + Read(true, false), + + /** + * Indicates that the resource is accessed for writing only. + */ + Write(false, true), + + /** + * Indicates that the resource is accessed for both reading and writing. + */ + ReadAndWrite(true, true); + + private final boolean read, write; + + private Access(boolean read, boolean write) { + this.read = read; + this.write = write; + } + + /** + * Returns true if the access is for reading. + * + * @return + */ + public boolean isRead() { + return read; + } + + /** + * Returns true if the access is for writing. + * + * @return + */ + public boolean isWrite() { + return write; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java index 73eb61542f..4a7e04a84c 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FGRenderContext.java @@ -326,7 +326,7 @@ public boolean isProfilerAvailable() { * * @return */ - public boolean isFrameCaptureActive() { + public boolean isGraphCaptureActive() { return renderManager.getGraphCapture() != null; } 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 6928ed2970..b744af0c81 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 @@ -40,14 +40,13 @@ import com.jme3.profile.FgStep; import com.jme3.profile.VpStep; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.RendererException; import com.jme3.renderer.ViewPort; 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; /** * Manages render passes, dependencies, and resources in a node-based parameter system. @@ -101,6 +100,7 @@ public class FrameGraph { private final HashMap settings = new HashMap<>(); private String name = "FrameGraph"; private boolean rendered = false; + private Exception renderException; /** * Creates a new blank framegraph. @@ -109,7 +109,7 @@ public class FrameGraph { */ public FrameGraph(AssetManager assetManager) { this.assetManager = assetManager; - this.resources = new ResourceList(); + this.resources = new ResourceList(this); this.context = new FGRenderContext(this); this.queues.add(new PassQueueExecutor(this, RENDER_THREAD)); } @@ -197,9 +197,14 @@ public boolean execute() { // execute if (prof != null) prof.vpStep(VpStep.FrameGraphExecute, vp, null); context.pushRenderSettings(); + renderException = null; for (PassQueueExecutor p : queues) { p.execute(context); } + if (renderException != null) { + renderException.printStackTrace(System.err); + throw new RendererException("An uncaught rendering exception occured, forcing the application to shut down."); + } context.popFrameBuffer(); // reset if (prof != null) prof.vpStep(VpStep.FrameGraphReset, vp, null); @@ -228,6 +233,16 @@ public void renderingComplete() { rendered = false; } + private PassQueueExecutor getQueue(int i) { + if (i >= queues.size()) { + PassQueueExecutor queue = new PassQueueExecutor(this, i); + queues.add(queue); + return queue; + } else { + return queues.get(i); + } + } + /** * Adds the pass to end of the pass queue. * @@ -236,7 +251,18 @@ public void renderingComplete() { * @return given pass */ public T add(T pass) { - return queues.get(RENDER_THREAD).add(pass); + return getQueue(RENDER_THREAD).add(pass); + } + /** + * + * + * @param + * @param pass + * @param threadIndex + * @return + */ + public T add(T pass, int threadIndex) { + return getQueue(threadIndex).add(pass); } /** * Adds the pass at the index in the pass queue. @@ -247,11 +273,12 @@ public T add(T pass) { * * @param * @param pass - * @param index + * @param threadIndex + * @param queueIndex * @return */ - public T add(T pass, int index) { - return queues.get(RENDER_THREAD).add(pass, index); + public T add(T pass, int threadIndex, int queueIndex) { + return getQueue(threadIndex).add(pass, queueIndex); } /** * Creates and adds an Attribute pass and links it to the given ticket. @@ -263,7 +290,7 @@ public T add(T pass, int index) { * @return created Attribute */ public Attribute addAttribute(ResourceTicket ticket) { - return queues.get(RENDER_THREAD).addAttribute(ticket); + return getQueue(RENDER_THREAD).addAttribute(ticket); } /** @@ -466,6 +493,19 @@ public void setCLQueue(CommandQueue clQueue) { context.setCLQueue(clQueue); } + /** + * Called internally when a rendering exception occurs. + * + * @param ex + */ + public void interruptRendering(Exception ex) { + assert ex != null : "Interrupting exception cannot be null."; + renderException = ex; + for (PassQueueExecutor q : queues) { + q.interrupt(); + } + } + /** * * @return diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphData.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphData.java index 2e144c9fae..abf4912805 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphData.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphData.java @@ -39,7 +39,7 @@ import com.jme3.export.SavableObject; import com.jme3.renderer.framegraph.passes.RenderPass; import java.io.IOException; -import java.util.Collection; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; @@ -54,22 +54,22 @@ public class FrameGraphData implements Savable { private static final String DEF_NAME = "FrameGraph"; - private static final RenderPass[] DEF_PASSES = new RenderPass[0]; + private static final ArrayList DEF_QUEUES = new ArrayList<>(0); private static final SavablePassConnection[] DEF_CONNECTIONS = new SavablePassConnection[0]; private static final HashMap DEF_SETTINGS = new HashMap<>(); private final boolean export; private String name; - private RenderPass[] passes; + private ArrayList queues; private SavablePassConnection[] connections; private Map settings; public FrameGraphData() { export = false; } - public FrameGraphData(FrameGraph fg, Collection passes, Map settings) { + public FrameGraphData(FrameGraph fg, ArrayList queues, Map settings) { this.name = fg.getName(); - this.passes = passes.toArray(new RenderPass[0]); + this.queues = queues; this.settings = new HashMap<>(); for (String key : settings.keySet()) { this.settings.put(key, new SavableObject(settings.get(key))); @@ -82,36 +82,42 @@ public void write(JmeExporter ex) throws IOException { if (!export) { throw new IllegalStateException("Data is import only."); } - if (passes == null) { + if (queues == null) { throw new IllegalStateException("Data is already consumed."); } final HashMap idMap = new HashMap<>(); final LinkedList list = new LinkedList<>(); int nextId = 0; // remap ids - for (RenderPass p : passes) { - p.setExportId(nextId++); - idMap.put(p.getId(), p.getExportId()); + for (PassQueueExecutor q : queues) { + for (RenderPass p : q) { + p.setExportId(nextId++); + idMap.put(p.getId(), p.getExportId()); + } } // extract connections - for (RenderPass p : passes) for (ResourceTicket t : p.getInputTickets()) { - if (t.hasSource()) { - int outId = idMap.get(t.getSource().getPassId()); - list.add(new SavablePassConnection(p.getExportId(), outId, t.getName(), t.getSource().getName())); + for (PassQueueExecutor q : queues) { + for (RenderPass p : q) for (ResourceTicket t : p.getInputTickets()) { + if (t.hasSource()) { + int outId = idMap.get(t.getSource().getPassId()); + list.add(new SavablePassConnection(p.getExportId(), outId, t.getName(), t.getSource().getName())); + } } } OutputCapsule out = ex.getCapsule(this); out.write(name, "name", DEF_NAME); - out.write(passes, "passes", DEF_PASSES); + out.writeSavableArrayList(queues, "passes", DEF_QUEUES); out.write(list.toArray(new SavablePassConnection[0]), "connections", DEF_CONNECTIONS); out.writeStringSavableMap(settings, "settings", DEF_SETTINGS); // reset export ids - for (RenderPass p : passes) { - p.setExportId(-1); + for (PassQueueExecutor q : queues) { + for (RenderPass p : q) { + p.setExportId(-1); + } } idMap.clear(); list.clear(); - passes = null; + queues = null; settings.clear(); } @Override @@ -122,13 +128,13 @@ public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); name = in.readString("name", "FrameGraph"); int baseId = RenderPass.getNextId(); - Savable[] array = in.readSavableArray("passes", new RenderPass[0]); - passes = new RenderPass[array.length]; - for (int i = 0; i < array.length; i++) { - RenderPass p = passes[i] = (RenderPass)array[i]; - p.shiftId(baseId); + queues = in.readSavableArrayList("passes", DEF_QUEUES); + for (PassQueueExecutor q : queues) { + for (RenderPass p : q) { + p.shiftId(baseId); + } } - array = in.readSavableArray("connections", new SavablePassConnection[0]); + Savable[] array = in.readSavableArray("connections", new SavablePassConnection[0]); connections = new SavablePassConnection[array.length]; for (int i = 0; i < array.length; i++) { SavablePassConnection c = connections[i] = (SavablePassConnection)array[i]; @@ -148,17 +154,17 @@ public void apply(FrameGraph fg) { if (export) { throw new IllegalStateException("Data is export only."); } - if (passes == null) { + if (queues == null) { throw new IllegalStateException("Data has already been consumed."); } fg.setName(name); - for (RenderPass p : passes) { - fg.add(p); - } // cache passes by id HashMap cache = new HashMap<>(); - for (RenderPass p : passes) { - cache.put(p.getId(), p); + for (PassQueueExecutor q : queues) { + for (RenderPass p : q) { + fg.add(p, q.getIndex()); + cache.put(p.getId(), p); + } } // read connections for (SavablePassConnection c : connections) { @@ -171,7 +177,7 @@ public void apply(FrameGraph fg) { fg.setSetting(key, settings.get(key).getObject()); } cache.clear(); - passes = null; + queues = null; connections = null; } @@ -183,13 +189,14 @@ public void apply(FrameGraph fg) { public boolean isExportOnly() { return export; } + /** * Returns true if this data has been consumed. * * @return */ public boolean isConsumed() { - return passes == null; + return queues == null; } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java index 53ee9169dc..22b3bc04d3 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/FrameGraphFactory.java @@ -41,9 +41,7 @@ import com.jme3.renderer.framegraph.passes.LightImagePass; import com.jme3.renderer.framegraph.passes.OutputRenderPass; import com.jme3.renderer.framegraph.passes.OutputPass; -import com.jme3.renderer.framegraph.passes.PostProcessingRenderPass; import com.jme3.renderer.framegraph.passes.SceneEnqueuePass; -import com.jme3.renderer.queue.RenderQueue; /** * Utility class for constructing common framegraphs. @@ -96,7 +94,7 @@ public static FrameGraph deferred(AssetManager assetManager, boolean tiled) { GBufferPass gbuf = fg.add(new GBufferPass()); Attribute tileInfoAttr = fg.add(new Attribute()); Junction tileJunct1 = fg.add(new Junction(1, 1)); - LightImagePass lightImg = fg.add(new LightImagePass()); + LightImagePass lightImg = fg.add(new LightImagePass(), 1); Junction lightJunct = fg.add(new Junction(1, 6)); Junction tileJunct2 = fg.add(new Junction(1, 2)); DeferredPass deferred = fg.add(new DeferredPass()); @@ -116,8 +114,8 @@ public static FrameGraph deferred(AssetManager assetManager, boolean tiled) { tileJunct1.makeInput(tileInfoAttr, Attribute.OUTPUT, Junction.getInput(0)); tileJunct1.setIndexSource(tileToggle); - lightImg.makeInput(gbuf, "Lights", "Lights"); - lightImg.makeInput(tileInfoAttr, Attribute.OUTPUT, "TileInfo"); + lightImg.makeInput(enqueue, "OpaqueLights", "Lights"); + lightImg.makeInput(tileJunct1, Junction.getOutput(), "TileInfo"); GraphSetting lightPackMethod = fg.setSetting("LightPackMethod", tiled ? 0 : -1, true); lightJunct.setName("LightPackMethod"); @@ -131,7 +129,7 @@ public static FrameGraph deferred(AssetManager assetManager, boolean tiled) { tileJunct2.setIndexSource(tileToggle); deferred.makeGroupInput(gbuf, "GBufferData", "GBufferData"); - deferred.makeInput(gbuf, "Lights", "Lights"); + deferred.makeInput(enqueue, "OpaqueLights", "Lights"); deferred.makeGroupInput(lightJunct, Junction.getOutput(), "LightTextures", 0, 0, 3); deferred.makeInput(lightJunct, Junction.getOutput(3), "NumLights"); deferred.makeInput(lightJunct, Junction.getOutput(4), "Ambient"); 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 index 5b9bff668c..9eb670537f 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassIndex.java @@ -27,28 +27,30 @@ public PassIndex set(PassIndex index) { } /** - * Shifts the thread index down one if the thread index is greater + * Shifts the thread index up or down one if the thread index is greater * than the given index. * * @param i + * @param pos * @return new thread index */ - public int shiftThread(int i) { + public int shiftThread(int i, boolean pos) { if (threadIndex > i) { - threadIndex--; + threadIndex += pos ? 1 : -1; } return threadIndex; } /** - * Shifts the queue index down one if the thread index is greater than + * Shifts the queue index up or down one if the thread index is greater than * the given index. * * @param i + * @param pos * @return new queue index */ - public int shiftQueue(int i) { + public int shiftQueue(int i, boolean pos) { if (queueIndex > i) { - queueIndex--; + queueIndex += pos ? 1 : -1; } return 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 index fc327c5253..29df3a77ac 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassQueueExecutor.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/PassQueueExecutor.java @@ -4,24 +4,36 @@ */ package com.jme3.renderer.framegraph; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; 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.io.IOException; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; +import java.util.concurrent.TimeoutException; /** * * @author codex */ -public class PassQueueExecutor implements Runnable, Iterable { +public class PassQueueExecutor implements Runnable, Iterable, Savable { + + private static long threadTimeoutMillis = 5000; + private static final ArrayList DEF_QUEUE = new ArrayList<>(0); private final FrameGraph frameGraph; private final LinkedList queue = new LinkedList<>(); private int index; private Thread thread; private FGRenderContext context; + private boolean complete = false; + private boolean interrupted = false; public PassQueueExecutor(FrameGraph frameGraph, int index) { this.frameGraph = frameGraph; @@ -30,34 +42,87 @@ public PassQueueExecutor(FrameGraph frameGraph, int 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(); - } - } + try { + execute(); + } catch (Exception ex) { + frameGraph.interruptRendering(ex); } } @Override public Iterator iterator() { return queue.iterator(); } + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule out = ex.getCapsule(this); + ArrayList list = new ArrayList<>(queue.size()); + list.addAll(queue); + out.writeSavableArrayList(list, "queue", DEF_QUEUE); + out.write(index, "index", 0); + } + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule in = im.getCapsule(this); + queue.addAll(in.readSavableArrayList("queue", DEF_QUEUE)); + index = in.readInt("index", 0); + } public void execute(FGRenderContext context) { - if (!isAsync()) { - run(); + complete = false; + this.context = context; + if (index == FrameGraph.RENDER_THREAD) { + execute(); } else { thread = new Thread(this); thread.start(); } } + @SuppressWarnings("UseSpecificCatch") + private void execute() { + try { + for (RenderPass p : queue) { + if (interrupted) { + return; + } + if (!p.isUsed()) { + continue; + } + if (index == FrameGraph.RENDER_THREAD) { + if (context.isProfilerAvailable()) { + context.getProfiler().fgStep(FgStep.Execute, p.getProfilerName()); + } + if (context.isGraphCaptureActive()) { + context.getGraphCapture().executeRenderPass(p.getIndex(), p.getProfilerName()); + } + } + if (frameGraph.isAsync()) { + // wait until all input resources are available for use before executing + long startMillis = System.currentTimeMillis(); + while (!p.allInputsAvailable(context)) { + if (interrupted) { + return; + } + if (System.currentTimeMillis()-startMillis >= threadTimeoutMillis) { + throw new TimeoutException("Execution thread "+index+" timed out on pass "+p); + } + } + } + p.executeRender(context); + if (index == FrameGraph.RENDER_THREAD) { + context.popRenderSettings(); + } + } + complete = true; + } catch (Exception ex) { + frameGraph.interruptRendering(ex); + } + } + + public void interrupt() { + interrupted = true; + } + /** * Adds the pass to end of the pass queue. * @@ -230,15 +295,41 @@ public void clear() { queue.clear(); } - public int shiftIndex(int i) { + public int shiftIndex(int i, boolean pos) { if (index > i) { - index--; + index += pos ? 1 : -1; } return index; } + public int size() { + return queue.size(); + } + public int getIndex() { + return index; + } public boolean isAsync() { return index != FrameGraph.RENDER_THREAD; } + public boolean isComplete() { + return complete; + } + + /** + * Sets the duration, in milliseconds, that executors will wait + * for pass inputs to be available before aborting execution. + *

+ * Timeouts can only occur when multiple threads are running. + *

+ * default=5000 (5 seconds) + * + * @param threadTimeoutMillis + */ + public static void setThreadTimeoutMillis(long threadTimeoutMillis) { + PassQueueExecutor.threadTimeoutMillis = threadTimeoutMillis; + } + public static long getThreadTimeoutMillis() { + return threadTimeoutMillis; + } } 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 c317152abf..2cfcbc9e90 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 @@ -52,7 +52,7 @@ public class RenderObject { private final long id; private final T object; - private final LinkedList reservations = new LinkedList<>(); + private final LinkedList reservations = new LinkedList<>(); private int timeoutDuration; private int timeout = 0; private boolean acquired = false; @@ -128,7 +128,7 @@ public void release() { * @param index */ public void reserve(PassIndex index) { - reservations.add(index); + reservations.add(new Reservation(index)); } /** * Disposes the internal object. @@ -146,11 +146,20 @@ public void dispose() { * @return */ public boolean claimReservation(PassIndex index) { - for (Iterator it = reservations.iterator(); it.hasNext();) { - if (it.next().equals(index)) { - it.remove(); - return true; - } + for (Reservation r : reservations) { + if (r.claim(index)) return true; + } + return false; + } + /** + * Returns true if this render object is reserved within the time frame. + * + * @param frame + * @return + */ + public boolean isReservedWithin(TimeFrame frame) { + for (Reservation r : reservations) { + if (r.violates(frame)) return true; } return false; } @@ -203,34 +212,6 @@ public T getObject() { public boolean isAcquired() { return acquired; } - /** - * Returns true if this render object is reserved at the given - * render pass index. - * - * @param index - * @return - */ - public boolean isReservedAt(PassIndex index) { - return reservations.contains(index); - } - /** - * Returns true if this render object is reserved within the time frame. - * - * @param frame - * @return - */ - public boolean isReservedWithin(TimeFrame frame) { - if (frame.getStartQueueIndex() >= reservations.size()) { - return false; - } - int n = Math.min(reservations.size()-1, frame.getEndQueueIndex()); - for (int i = frame.getStartQueueIndex(); i <= n; i++) { - if (reservations.get(i)) { - return true; - } - } - return false; - } /** * Returns true if this render object is constant. * @@ -249,4 +230,26 @@ public static long getNextId() { return nextId; } + private static class Reservation { + + private final PassIndex index; + private boolean claimed = false; + + public Reservation(PassIndex index) { + this.index = index; + } + + public boolean claim(PassIndex index) { + if (this.index.equals(index)) { + claimed = true; + return true; + } + return false; + } + public boolean violates(TimeFrame frame) { + return !claimed && (frame.isAsync() || frame.getThreadIndex() != index.getThreadIndex()); + } + + } + } 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 ad8eb2c6be..6ec0a1c10b 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 @@ -72,36 +72,15 @@ public RenderObjectMap(RenderManager renderManager, boolean async) { } } - /** - * Creates a new render object with a new internal object. - * - * @param - * @param def - * @return - */ - protected RenderObject create(ResourceDef def) { + private RenderObject create(ResourceDef def) { return create(def, def.createResource()); } - /** - * Creates a new render object with the given internal object. - * - * @param - * @param def - * @param value internal object - * @return - */ - protected RenderObject create(ResourceDef def, T value) { + private RenderObject create(ResourceDef def, T value) { RenderObject obj = new RenderObject(def, value, staticTimeout); objectMap.put(obj.getId(), obj); return obj; } - /** - * Returns true if the render object is available for reallocation. - * - * @param object - * @return - */ - protected boolean isAvailable(RenderObject object) { + private boolean isAvailable(RenderObject object) { return !object.isAcquired() && !object.isConstant(); } @@ -188,7 +167,7 @@ private boolean allocateSpecificSync(RenderResource resource) { RenderObject obj = objectMap.get(id); if (obj != null) { if (cap != null) cap.attemptReallocation(id, resource.getIndex()); - if (isAvailable(obj) && (obj.isReservedAt(resource.getLifeTime().getStartQueueIndex()) + if (isAvailable(obj) && (obj.claimReservation(resource.getProducer().getExecutionIndex()) || !obj.isReservedWithin(resource.getLifeTime()))) { // reserved object is only applied if it is accepted by the definition T r = def.applyDirectResource(obj.getObject()); @@ -204,7 +183,6 @@ private boolean allocateSpecificSync(RenderResource resource) { return true; } } - if (cap != null) cap.allocateSpecificFailed(obj, resource); } failedReservations++; return false; @@ -312,8 +290,8 @@ private boolean allocateSpecificAsync(RenderResource resource) { 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()))) { + if (obj.claimReservation(resource.getProducer().getExecutionIndex()) + || !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) { @@ -331,7 +309,6 @@ private boolean allocateSpecificAsync(RenderResource resource) { } obj.endInspect(); } - if (cap != null) cap.allocateSpecificFailed(obj, resource); } failedReservations++; return false; 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 e7dbacdbb1..f324676088 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,21 +42,23 @@ */ public class RenderResource { - private ResourceProducer producer; - private ResourceDef def; - private ResourceTicket ticket; - private TimeFrame lifetime; + private final ResourceProducer producer; + private final ResourceDef def; + private final ResourceTicket ticket; + private final TimeFrame lifetime; private RenderObject object; private T resource; private int refs = 0; private boolean survivesRefCull = false; private boolean undefined = false; + private boolean written = false; /** * * @param producer * @param def * @param ticket + * @param async */ public RenderResource(ResourceProducer producer, ResourceDef def, ResourceTicket ticket) { this.producer = producer; @@ -74,11 +76,21 @@ public void reference(PassIndex index) { lifetime.extendTo(index); refs++; } + /** + * + * @return + */ + public boolean isAvailable() { + return (!lifetime.isAsync() || !written) && !isVirtual(); + } /** * Releases this resource from one user. + * + * @return true if this resource is used after the release */ - public void release() { - refs--; + public boolean release() { + written = false; + return --refs >= 0; } /** 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 26f6e1fe29..e3cd1fe02a 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 @@ -48,45 +48,24 @@ public class ResourceList { private static final int INITIAL_SIZE = 20; + private final FrameGraph frameGraph; private RenderManager renderManager; 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; - public ResourceList() {} - public ResourceList(RenderObjectMap map) { - this.map = map; + public ResourceList(FrameGraph frameGraph) { + this.frameGraph = frameGraph; } - /** - * Creates and adds a new render resource. - * - * @param - * @param producer - * @param def - * @return new render resource - */ - protected RenderResource create(ResourceProducer producer, ResourceDef def) { + private RenderResource create(ResourceProducer producer, ResourceDef def) { RenderResource res = new RenderResource<>(producer, def, new ResourceTicket<>()); res.getTicket().setLocalIndex(add(res)); return res; } - - /** - * Locates the resource associated with the ticket. - * - * @param - * @param ticket ticket to locate with (not null) - * @return located resource - * @throws NullPointerException if ticket is null - * @throws NullPointerException if ticket's world index is negative - * @throws NullPointerException if ticket points to a null resource - * @throws IndexOutOfBoundsException if ticket's world index is >= size - */ - protected RenderResource locate(ResourceTicket ticket) { + private RenderResource locate(ResourceTicket ticket) { if (ticket == null) { throw new NullPointerException("Ticket cannot be null."); } @@ -103,26 +82,7 @@ protected RenderResource locate(ResourceTicket ticket) { } throw new IndexOutOfBoundsException(ticket+" is out of bounds for size "+resources.size()); } - - /** - * Returns true if the ticket can be used to locate a resource. - *

- * Use {@link ResourceTicket#validate(com.jme3.renderer.framegraph.ResourceTicket)} instead. - * - * @param ticket - * @return - */ - public boolean validate(ResourceTicket ticket) { - return ResourceTicket.validate(ticket); - } - - /** - * Adds the resource to the first available slot. - * - * @param res - * @return - */ - protected int add(RenderResource res) { + private int add(RenderResource res) { assert res != null; if (nextSlot >= resources.size()) { // addUserEvent resource to end of list @@ -143,14 +103,7 @@ protected int add(RenderResource res) { return i; } } - - /** - * Removes the resource at the index. - * - * @param index - * @return - */ - protected RenderResource remove(int index) { + private RenderResource remove(int index) { RenderResource prev = resources.set(index, null); if (prev != null && prev.isReferenced()) { throw new IllegalStateException("Cannot remove "+prev+" because it is referenced."); @@ -159,6 +112,18 @@ protected RenderResource remove(int index) { return prev; } + /** + * Returns true if the ticket can be used to locate a resource. + *

+ * Use {@link ResourceTicket#validate(com.jme3.renderer.framegraph.ResourceTicket)} instead. + * + * @param ticket + * @return + */ + public boolean validate(ResourceTicket ticket) { + return ResourceTicket.validate(ticket); + } + /** * Declares a new resource. * @@ -345,6 +310,34 @@ public boolean isVirtual(ResourceTicket ticket, boolean optional) { return true; } + /** + * Returns true if the resource at the ticket is available for use. + *

+ * This is used for asynchronous situations. + * + * @param ticket + * @return + */ + public boolean isAvailable(ResourceTicket ticket) { + if (ResourceTicket.validate(ticket)) { + return locate(ticket).isAvailable(); + } + return true; + } + + /** + * Returns true if the resource at the ticket is asynchronous. + * + * @param ticket + * @return + */ + public boolean isAsync(ResourceTicket ticket) { + if (ResourceTicket.validate(ticket)) { + return locate(ticket).getLifeTime().isAsync(); + } + return false; + } + /** * Acquires the object held by the given resource. *

@@ -551,9 +544,8 @@ public void setPrimitive(ResourceTicket ticket, T value) { */ public void release(ResourceTicket ticket) { RenderResource resource = locate(ticket); - resource.release(); if (cap != null) cap.releaseResource(resource.getIndex(), ticket.getName()); - if (!resource.isUsed()) { + if (!resource.release()) { if (cap != null && resource.getObject() != null) { cap.releaseObject(resource.getObject().getId()); } 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 d4e0ee99fa..b9ba7527b0 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 @@ -6,6 +6,7 @@ import com.jme3.renderer.Camera; import com.jme3.renderer.ViewPort; +import com.jme3.renderer.framegraph.PassIndex; import com.jme3.renderer.framegraph.RenderObject; import com.jme3.renderer.framegraph.RenderResource; import com.jme3.texture.FrameBuffer; @@ -51,10 +52,10 @@ public void renderViewPort(ViewPort vp) { Camera cam = vp.getCamera(); add(new Event("SUPEREVENT", "StartViewPort", vp.getName(), cam.getWidth(), cam.getHeight())); } - public void prepareRenderPass(int index, String name) { + public void prepareRenderPass(PassIndex index, String name) { events.add(new Event("PrepareRenderPass", index, name)); } - public void executeRenderPass(int index, String name) { + public void executeRenderPass(PassIndex index, String name) { add(new Event("ExecuteRenderPass", index, name)); } public void createFrameBuffer(FrameBuffer fb) { @@ -83,7 +84,7 @@ public void bindTexture(int index, String ticket) { add(new Event("BindTexture", index, ticket)); } - public void reserveObject(long id, int index) { + public void reserveObject(long id, PassIndex index) { add(new Event("ReserveObject", id, index)); } public void createObject(long id, int index, String type) { @@ -98,15 +99,6 @@ public void reallocateObject(long id, int index, String type) { public void attemptReallocation(long id, int index) { add(new Event("AttemptSpecificReallocation", id, index)); } - public void allocateSpecificFailed(RenderObject object, RenderResource resource) { - add(new Failure("AllocateSpecific", - new Check("nullObject", () -> object != null, true), - new Check("acquired", () -> !object.isAcquired()), - new Check("constant", () -> !object.isConstant()), - new Check("conflicting", () -> object.isReservedAt(resource.getLifeTime().getStartQueueIndex()) - || !object.isReservedWithin(resource.getLifeTime())) - )); - } public void setObjectConstant(long id) { add(new Event("SetObjectConstant", id)); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java index 70023b6e77..f2e81b8950 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/DeferredPass.java @@ -186,6 +186,9 @@ protected void cleanup(FrameGraph frameGraph) {} public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, EnumSet rendererCaps, LightList lights, DefineList defines) { // defines should only be set in this method + if (defines.size() == 0) { + defs.config(material.getActiveTechnique().getDef()); + } if (lightTextures[0] == null) { ColorRGBA amb = resources.acquireOrElse(ambient, null); if (amb == null) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java index 451b0a760a..346fa4b6ac 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/GBufferPass.java @@ -66,9 +66,7 @@ public class GBufferPass extends RenderPass implements GeometryRenderHandler { private ResourceTicket geometry; private ResourceTicket[] gbuffers; - private ResourceTicket lights; private ResourceTicket numRendersTicket; - private ValueDef lightDef; private final TextureDef[] texDefs = new TextureDef[5]; private int numRenders = 0; @@ -76,7 +74,6 @@ public class GBufferPass extends RenderPass implements GeometryRenderHandler { protected void initialize(FrameGraph frameGraph) { geometry = addInput("Geometry"); gbuffers = addOutputGroup("GBufferData", 5); - lights = addOutput("Lights"); numRendersTicket = addOutput("NumRenders"); Function tex = img -> new Texture2D(img); texDefs[0] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA16F); @@ -84,8 +81,6 @@ protected void initialize(FrameGraph frameGraph) { texDefs[2] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA16F); texDefs[3] = new TextureDef<>(Texture2D.class, tex, Image.Format.RGBA32F); texDefs[4] = new TextureDef<>(Texture2D.class, tex, Image.Format.Depth); - lightDef = new ValueDef(LightList.class, n -> new LightList(null)); - lightDef.setReviser(list -> list.clear()); } @Override protected void prepare(FGRenderContext context) { @@ -94,7 +89,6 @@ protected void prepare(FGRenderContext context) { texDefs[i].setSize(w, h); declare(texDefs[i], gbuffers[i]); } - declare(lightDef, lights); declare(null, numRendersTicket); reserve(gbuffers); reference(geometry); @@ -110,18 +104,11 @@ protected void execute(FGRenderContext context) { resources.acquireDepthTarget(fb, gbuffers[4]); context.getRenderer().setFrameBuffer(fb); context.getRenderer().clearBuffers(true, true, true); - LightList lightList = resources.acquire(lights); context.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha); context.getRenderManager().setForcedTechnique(GBUFFER_PASS); context.getRenderManager().setGeometryRenderHandler(this); GeometryList bucket = resources.acquire(geometry); context.renderGeometryList(bucket, null, this); - // get lights for all rendered geometries - for (Spatial s : new ParentIterator(bucket)) { - for (Light l : s.getLocalLightList()) { - lightList.add(l); - } - } resources.setPrimitive(numRendersTicket, numRenders); } @Override 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 2e112eaa20..96b975880d 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 @@ -97,18 +97,24 @@ public void initializePass(FrameGraph frameGraph, PassIndex index) { * @param context */ public void prepareRender(FGRenderContext context) { - if (index < 0) { + if (index == null) { throw new IllegalStateException("Pass is not properly initialized for rendering."); } prepare(context); + // set the flag for checking if resources are available } /** * * @param context * @return */ - public boolean asyncWait(FGRenderContext context) { - + public boolean allInputsAvailable(FGRenderContext context) { + for (ResourceTicket t : inputs) { + if (!resources.isAvailable(t)) { + return false; + } + } + return true; } /** * Executes the pass. @@ -147,7 +153,7 @@ public void cleanupPass(FrameGraph frameGraph) { inputs.clear(); outputs.clear(); groups.clear(); - index = -1; + index = null; this.frameGraph = null; } @@ -795,9 +801,7 @@ public void countReferences() { * @param positive */ public void shiftExecutionIndex(int threshold, boolean positive) { - if (index > threshold) { - index += (positive ? 1 : -1); - } + index.shiftQueue(threshold, positive); } /** * Shifts the id of this pass. @@ -871,7 +875,7 @@ public int getExportId() { * * @return */ - public int getIndex() { + public PassIndex getIndex() { return index; } /** @@ -880,7 +884,7 @@ public int getIndex() { * @return */ public boolean isAssigned() { - return index >= 0; + return index != null; } /** * Gets the number of ticket groups. diff --git a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java index 1ea93c8678..6312654062 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java +++ b/jme3-core/src/main/java/com/jme3/renderer/framegraph/passes/SceneEnqueuePass.java @@ -8,6 +8,8 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; +import com.jme3.light.Light; +import com.jme3.light.LightList; import com.jme3.renderer.Camera; import com.jme3.renderer.ViewPort; import com.jme3.renderer.framegraph.FGRenderContext; @@ -51,13 +53,15 @@ public SceneEnqueuePass(boolean runControlRender) { @Override protected void initialize(FrameGraph frameGraph) { for (Bucket b : buckets) { - b.ticket = addOutput(b.name); + b.geometry = addOutput(b.name); + b.lights = addOutput(b.name+"Lights"); } } @Override protected void prepare(FGRenderContext context) { for (Bucket b : buckets) { - declare(null, b.ticket); + declare(null, b.geometry); + declare(null, b.lights); } } @Override @@ -69,13 +73,15 @@ protected void execute(FGRenderContext context) { queueSubScene(context, scenes.get(i), null); } for (Bucket b : buckets) { - resources.setPrimitive(b.ticket, b.queue); + resources.setPrimitive(b.geometry, b.queue); + resources.setPrimitive(b.lights, b.lightList); } } @Override protected void reset(FGRenderContext context) { for (Bucket b : buckets) { b.queue.clear(); + b.lightList.clear(); } } @Override @@ -111,6 +117,12 @@ private void queueSubScene(FGRenderContext context, Spatial scene, RenderQueue.B b = scene.getQueueBucket(); } } + // accumulate lights + Bucket bucket = getBucket(b); + for (Light l : scene.getLocalLightList()) { + bucket.lightList.add(l); + } + // add to bucket if (scene instanceof Node) { Node n = (Node)scene; int camState = cam.getPlaneState(); @@ -125,7 +137,7 @@ private void queueSubScene(FGRenderContext context, Spatial scene, RenderQueue.B if (g.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + g.getName()); } - getBucket(b).queue.add(g); + bucket.queue.add(g); } } private Bucket getBucket(RenderQueue.Bucket bucket) { @@ -143,11 +155,14 @@ private static class Bucket { public final String name; public final GeometryList queue; - public ResourceTicket ticket; + public final LightList lightList; + public ResourceTicket geometry; + public ResourceTicket lights; public Bucket(String name, GeometryComparator comparator) { this.name = name; this.queue = new GeometryList(comparator); + this.lightList = new LightList(null); } } diff --git a/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java b/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java index 30add22d54..031eff38fa 100644 --- a/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java +++ b/jme3-examples/src/main/java/jme3test/renderpath/TestSimpleDeferredLighting.java @@ -662,7 +662,7 @@ public void simpleInitApp() { //guiViewPort.setEnabled(false); //FrameGraph graph = RenderPipelineFactory.create(this, RenderManager.RenderPath.Deferred); - //FrameGraph forward = new FrameGraph(assetManager, "Common/FrameGraphs/Forward.j3g"); + FrameGraph forward = FrameGraphFactory.forward(assetManager); //forward.setName("forward"); //FrameGraph deferred = new FrameGraph(assetManager, "Common/FrameGraphs/Deferred.j3g"); FrameGraph deferred = FrameGraphFactory.deferred(assetManager, false);