diff --git a/chunky/src/java/se/llbit/chunky/main/Chunky.java b/chunky/src/java/se/llbit/chunky/main/Chunky.java index f3b3cb620f..19a3029881 100644 --- a/chunky/src/java/se/llbit/chunky/main/Chunky.java +++ b/chunky/src/java/se/llbit/chunky/main/Chunky.java @@ -217,7 +217,7 @@ public static void main(final String[] args) { if (cmdline.mode == CommandLineOptions.Mode.CLI_OPERATION) { exitCode = cmdline.exitCode; } else { - commonThreads = new ForkJoinPool(PersistentSettings.getNumThreads()); + commonThreads = new ForkJoinPool(PersistentSettings.getRenderThreadCount()); Chunky chunky = new Chunky(cmdline.options); chunky.headless = cmdline.mode == Mode.HEADLESS_RENDER || cmdline.mode == Mode.CREATE_SNAPSHOT; @@ -341,7 +341,7 @@ public void update() { */ public static ForkJoinPool getCommonThreads() { if (commonThreads == null) { - commonThreads = new ForkJoinPool(PersistentSettings.getNumThreads()); + commonThreads = new ForkJoinPool(PersistentSettings.getRenderThreadCount()); } return commonThreads; } diff --git a/chunky/src/java/se/llbit/chunky/main/ChunkyOptions.java b/chunky/src/java/se/llbit/chunky/main/ChunkyOptions.java index ac2a19984f..743f73381e 100644 --- a/chunky/src/java/se/llbit/chunky/main/ChunkyOptions.java +++ b/chunky/src/java/se/llbit/chunky/main/ChunkyOptions.java @@ -17,12 +17,14 @@ package se.llbit.chunky.main; import se.llbit.chunky.PersistentSettings; -import se.llbit.chunky.renderer.RenderConstants; +import se.llbit.chunky.renderer.RenderOptions; +import se.llbit.chunky.renderer.ModifiableRenderOptions; import se.llbit.chunky.renderer.scene.Scene; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** * Various options for Chunky, set via the configuration file and/or command-line flags. @@ -37,12 +39,10 @@ public class ChunkyOptions { public String imageOutputFile = ""; private List resourcePacks = new ArrayList<>(); - public int renderThreads = -1; public File worldDir = null; public int target = -1; - public int tileWidth = RenderConstants.TILE_WIDTH_DEFAULT; - public int sppPerPass = RenderConstants.SPP_PER_PASS_DEFAULT; + protected ModifiableRenderOptions renderOptions = new ModifiableRenderOptions(); /** Ignore scene loading errors when starting a headless render. */ public boolean force = false; @@ -56,7 +56,7 @@ private ChunkyOptions() { public static ChunkyOptions getDefaults() { ChunkyOptions defaults = new ChunkyOptions(); defaults.sceneDir = PersistentSettings.getSceneDirectory(); - defaults.renderThreads = PersistentSettings.getNumThreads(); + defaults.renderOptions.setRenderThreadCount(PersistentSettings.getRenderThreadCount()); return defaults; } @@ -65,11 +65,24 @@ public static ChunkyOptions getDefaults() { clone.sceneDir = sceneDir; clone.sceneName = sceneName; clone.resourcePacks = new ArrayList<>(resourcePacks); - clone.renderThreads = renderThreads; + clone.renderOptions.copyState(renderOptions); clone.worldDir = worldDir; return clone; } + /** + * Returns the current state of the render options to by the renderer. + * It is not specified whether it is a copy or a view of the current options and + * does not guarantee to update on change. + *

Cast it to {@link ModifiableRenderOptions}, if you have to edit it. + * Edits are only safe on startup and might not propagate while a render is in progress. + * + * @return Options to use to start the render system. + */ + public RenderOptions getRenderOptions() { + return renderOptions; + } + public List getResourcePacks() { return resourcePacks; } diff --git a/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java b/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java index 5fcfcc5d15..5f72d35aa3 100644 --- a/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java +++ b/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java @@ -20,7 +20,9 @@ import se.llbit.chunky.PersistentSettings; import se.llbit.chunky.renderer.ConsoleProgressListener; +import se.llbit.chunky.renderer.ModifiableRenderOptions; import se.llbit.chunky.renderer.RenderContext; +import se.llbit.chunky.renderer.RenderOptions; import se.llbit.chunky.renderer.scene.Scene; import se.llbit.chunky.resources.ResourcePackLoader; import se.llbit.json.JsonNumber; @@ -251,7 +253,7 @@ List handle(List args) throws InvalidCommandLineArgumentsExcepti )); } } - consumer.accept(new ArrayList<>(optionArguments)); // Create copy to avoid side effects. + consumer.accept(Collections.unmodifiableList(optionArguments)); return arguments; } } @@ -293,14 +295,16 @@ public CommandLineOptions(String[] args) { registerOption("-target", new Range(1), arguments -> options.target = Math.max(1, Integer.parseInt(arguments.get(0)))); + ModifiableRenderOptions renderOptions = (ModifiableRenderOptions) options.getRenderOptions(); + registerOption("-threads", new Range(1), - arguments -> options.renderThreads = Math.max(1, Integer.parseInt(arguments.get(0)))); + arguments -> renderOptions.setRenderThreadCount(Math.max(1, Integer.parseInt(arguments.get(0))))); registerOption("-tile-width", new Range(1), - arguments -> options.tileWidth = Math.max(1, Integer.parseInt(arguments.get(0)))); + arguments -> renderOptions.setTileWidth(Math.max(1, Integer.parseInt(arguments.get(0))))); registerOption("-spp-per-pass", new Range(1), - arguments -> options.sppPerPass = Math.max(1, Integer.parseInt(arguments.get(0)))); + arguments -> renderOptions.setSppPerPass(Math.max(1, Integer.parseInt(arguments.get(0))))); registerOption("-version", new Range(0), arguments -> { mode = Mode.CLI_OPERATION; diff --git a/chunky/src/java/se/llbit/chunky/map/WorldMapLoader.java b/chunky/src/java/se/llbit/chunky/map/WorldMapLoader.java index 30b8c6359a..afb72bd37d 100644 --- a/chunky/src/java/se/llbit/chunky/map/WorldMapLoader.java +++ b/chunky/src/java/se/llbit/chunky/map/WorldMapLoader.java @@ -56,7 +56,9 @@ public WorldMapLoader(ChunkyFxController controller, MapView mapView) { mapView.addViewListener(this); // Start worker threads. - RegionParser[] regionParsers = new RegionParser[Integer.parseInt(System.getProperty("chunky.mapLoaderThreads", String.valueOf(PersistentSettings.getNumThreads())))]; + RegionParser[] regionParsers = new RegionParser[Integer.parseInt( + System.getProperty("chunky.mapLoaderThreads", String.valueOf(PersistentSettings.getMapLoadingThreadCount())) + )]; for (int i = 0; i < regionParsers.length; ++i) { regionParsers[i] = new RegionParser(this, regionQueue, mapView); regionParsers[i].start(); diff --git a/chunky/src/java/se/llbit/chunky/renderer/DefaultRenderManager.java b/chunky/src/java/se/llbit/chunky/renderer/DefaultRenderManager.java index aef07453de..5d93ac6bb6 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/DefaultRenderManager.java +++ b/chunky/src/java/se/llbit/chunky/renderer/DefaultRenderManager.java @@ -186,7 +186,9 @@ public DefaultRenderManager(RenderContext context, boolean headless) { this.bufferedScene = context.getChunky().getSceneFactory().newScene(); // Create a new pool. Set the seed to the current time in milliseconds. - this.pool = context.renderPoolFactory.create(context.numRenderThreads(), System.currentTimeMillis()); + this.pool = context.renderPoolFactory.create( + context.getRenderOptions().getRenderThreadCount(), System.currentTimeMillis() + ); this.setCPULoad(PersistentSettings.getCPULoad()); // Initialize callbacks here since java will complain `bufferedScene` is not initialized yet. @@ -294,7 +296,7 @@ public void run() { getPreviewRenderer().sceneReset(this, reason, resetCount); // Select the correct renderer - Renderer render = mode == RenderMode.PREVIEW ? getPreviewRenderer() : getRenderer(); + Renderer renderer = mode == RenderMode.PREVIEW ? getPreviewRenderer() : getRenderer(); frameStart = System.currentTimeMillis(); if (mode == RenderMode.PREVIEW) { @@ -302,8 +304,8 @@ public void run() { if (finalizeAllFrames) { // Preview with no CPU limit pool.setCpuLoad(100); - render.setPostRender(previewCallback); - render.render(this); + renderer.setPostRender(previewCallback); + startRender(renderer); pool.setCpuLoad(cpuLoad); } } else { @@ -314,8 +316,8 @@ public void run() { updateRenderState(scene); }); } else if (mode != RenderMode.PAUSED) { - render.setPostRender(renderCallback); - render.render(this); + renderer.setPostRender(renderCallback); + startRender(renderer); } } @@ -328,6 +330,10 @@ public void run() { } } + protected void startRender(Renderer renderer) throws InterruptedException { + renderer.render(this); + } + /** * Redraw the GUI screen. This should be run after postprocessing. */ diff --git a/chunky/src/java/se/llbit/chunky/renderer/ModifiableRenderOptions.java b/chunky/src/java/se/llbit/chunky/renderer/ModifiableRenderOptions.java new file mode 100644 index 0000000000..de747e5124 --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/renderer/ModifiableRenderOptions.java @@ -0,0 +1,40 @@ +package se.llbit.chunky.renderer; + +public class ModifiableRenderOptions implements RenderOptions { + protected int renderThreadCount = -1; + protected int tileWidth = TILE_WIDTH_DEFAULT; + protected int sppPerPass = SPP_PER_PASS_DEFAULT; + + public void copyState(RenderOptions other) { + renderThreadCount = other.getRenderThreadCount(); + tileWidth = other.getTileWidth(); + sppPerPass = other.getSppPerPass(); + } + + @Override + public int getRenderThreadCount() { + return renderThreadCount; + } + + public void setRenderThreadCount(int renderThreadCount) { + this.renderThreadCount = renderThreadCount; + } + + @Override + public int getTileWidth() { + return tileWidth; + } + + public void setTileWidth(int tileWidth) { + this.tileWidth = tileWidth; + } + + @Override + public int getSppPerPass() { + return sppPerPass; + } + + public void setSppPerPass(int sppPerPass) { + this.sppPerPass = sppPerPass; + } +} diff --git a/chunky/src/java/se/llbit/chunky/renderer/PathTracingRenderer.java b/chunky/src/java/se/llbit/chunky/renderer/PathTracingRenderer.java index 946eb5a92f..4c70b1e5d9 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/PathTracingRenderer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/PathTracingRenderer.java @@ -60,7 +60,7 @@ public void render(DefaultRenderManager manager) throws InterruptedException { int cropX = scene.getCropX(); int cropY = scene.getCropY(); - int sppPerPass = manager.context.sppPerPass(); + int sppPerPass = manager.context.getRenderOptions().getSppPerPass(); Camera cam = scene.camera(); double halfWidth = fullWidth / (2.0 * fullHeight); double invHeight = 1.0 / fullHeight; diff --git a/chunky/src/java/se/llbit/chunky/renderer/RenderContext.java b/chunky/src/java/se/llbit/chunky/renderer/RenderContext.java index 42961af124..5d4599a1bf 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/RenderContext.java +++ b/chunky/src/java/se/llbit/chunky/renderer/RenderContext.java @@ -64,11 +64,8 @@ public File getSceneDirectory() { return sceneDirectory; } - /** - * @return The preferred number of rendering threads. - */ - public int numRenderThreads() { - return config.renderThreads; + public RenderOptions getRenderOptions() { + return config.getRenderOptions(); } /** @@ -84,18 +81,4 @@ public File getSceneFile(String fileName) { } return new File(sceneDirectory, fileName); } - - /** - * @return The tile width. - */ - public int tileWidth() { - return config.tileWidth; - } - - /** - * @return The samples per pixel per pass - */ - public int sppPerPass() { - return config.sppPerPass; - } } diff --git a/chunky/src/java/se/llbit/chunky/renderer/TileBasedRenderer.java b/chunky/src/java/se/llbit/chunky/renderer/TileBasedRenderer.java index 3750118cc7..518122b73a 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/TileBasedRenderer.java +++ b/chunky/src/java/se/llbit/chunky/renderer/TileBasedRenderer.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; +import java.util.function.Supplier; /** * A tile based renderer. Simply call {@code submitTiles} to submit a frame's worth of tiles to the work queue. @@ -57,6 +58,9 @@ public void setPostRender(BooleanSupplier callback) { postRender = callback; } + protected Supplier workerStateFactory = WorkerState::new; + protected Supplier rayFactory = Ray::new; + /** * Create and submit tiles to the rendering pool. * Await for these tiles to finish rendering with {@code manager.pool.awaitEmpty()}. @@ -65,12 +69,12 @@ public void setPostRender(BooleanSupplier callback) { * The second argument is the current pixel (x, y). */ protected void submitTiles(DefaultRenderManager manager, BiConsumer perPixel) { - initTiles(manager); + initTiles(manager.bufferedScene, manager.context.getRenderOptions()); cachedTiles.forEach(tile -> manager.pool.submit(worker -> { - WorkerState state = new WorkerState(); - state.ray = new Ray(); + WorkerState state = workerStateFactory.get(); + state.ray = rayFactory.get(); state.ray.setNormal(0, 0, -1); state.random = worker.random; @@ -86,11 +90,10 @@ protected void submitTiles(DefaultRenderManager manager, BiConsumer { - PersistentSettings.setNumRenderThreads(value); + PersistentSettings.setRenderThreadCount(value); renderControls.showPopup("This change takes effect after restarting Chunky.", renderThreads); }); @@ -309,8 +309,8 @@ public boolean shutdownAfterCompletedRender() { public void update(Scene scene) { outputMode.getSelectionModel().select(scene.getOutputMode()); fastFog.setSelected(scene.fog.fastFog()); + renderThreads.set(PersistentSettings.getRenderThreadCount()); transmissivityCap.set(scene.getTransmissivityCap()); - renderThreads.set(PersistentSettings.getNumThreads()); cpuLoad.set(PersistentSettings.getCPULoad()); rayDepth.set(scene.getRayDepth()); octreeImplementation.getSelectionModel().select(scene.getOctreeImplementation()); diff --git a/chunky/src/test/se/llbit/chunky/renderer/BlankRenderTest.java b/chunky/src/test/se/llbit/chunky/renderer/BlankRenderTest.java index 177a91eca1..fc78a5d89d 100644 --- a/chunky/src/test/se/llbit/chunky/renderer/BlankRenderTest.java +++ b/chunky/src/test/se/llbit/chunky/renderer/BlankRenderTest.java @@ -70,7 +70,7 @@ private static double[] render(Scene scene) throws InterruptedException { // A single worker thread is used, with fixed PRNG seed. // This makes the path tracing results deterministic. ChunkyOptions options = ChunkyOptions.getDefaults(); - options.renderThreads = 1; + ((ModifiableRenderOptions) options.getRenderOptions()).setRenderThreadCount(1); Chunky chunky = new Chunky(options); RenderContext context = new RenderContext(chunky); context.renderPoolFactory = (threads, seed) -> new RenderWorkerPool(threads, 0); diff --git a/lib/src/se/llbit/chunky/PersistentSettings.java b/lib/src/se/llbit/chunky/PersistentSettings.java index 932c1ef70b..3b03f626e3 100644 --- a/lib/src/se/llbit/chunky/PersistentSettings.java +++ b/lib/src/se/llbit/chunky/PersistentSettings.java @@ -24,13 +24,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import se.llbit.chunky.renderer.RenderConstants; +import se.llbit.chunky.renderer.RenderOptions; import se.llbit.chunky.resources.SettingsDirectory; import se.llbit.fxutil.WindowPosition; import se.llbit.json.JsonArray; import se.llbit.json.JsonObject; import se.llbit.json.JsonValue; -import se.llbit.log.Log; /** * Utility class for managing global Chunky settings. @@ -147,17 +146,24 @@ public static File getSceneDirectory() { /** * @return Default number of render threads */ - public static int getNumThreads() { - return settings.getInt("numThreads", RenderConstants.NUM_RENDER_THREADS_DEFAULT); + public static int getRenderThreadCount() { + return settings.getInt("numThreads", RenderOptions.RENDER_THREAD_COUNT_DEFAULT); + } + + /** + * @return Default number of map loading threads + */ + public static int getMapLoadingThreadCount() { + return getRenderThreadCount(); // TODO } /** * Set default number of render threads. */ - public static void setNumRenderThreads(int numThreads) { - numThreads = Math.max(RenderConstants.NUM_RENDER_THREADS_MIN, numThreads); - numThreads = Math.min(RenderConstants.NUM_RENDER_THREADS_MAX, numThreads); - settings.setInt("numThreads", numThreads); + public static void setRenderThreadCount(int renderThreadCount) { + renderThreadCount = Math.max(RenderOptions.RENDER_THREAD_COUNT_MIN, renderThreadCount); + renderThreadCount = Math.min(RenderOptions.RENDER_THREAD_COUNT_MAX, renderThreadCount); + settings.setInt("numThreads", renderThreadCount); save(); } @@ -165,7 +171,7 @@ public static void setNumRenderThreads(int numThreads) { * @return CPU load setting */ public static int getCPULoad() { - return settings.getInt("cpuLoad", RenderConstants.CPU_LOAD_DEFAULT); + return settings.getInt("cpuLoad", RenderOptions.TARGET_CPU_LOAD_PERCENTAGE); } /** diff --git a/lib/src/se/llbit/chunky/renderer/RenderConstants.java b/lib/src/se/llbit/chunky/renderer/RenderOptions.java similarity index 68% rename from lib/src/se/llbit/chunky/renderer/RenderConstants.java rename to lib/src/se/llbit/chunky/renderer/RenderOptions.java index 18ed5754c0..0b049e0586 100644 --- a/lib/src/se/llbit/chunky/renderer/RenderConstants.java +++ b/lib/src/se/llbit/chunky/renderer/RenderOptions.java @@ -16,36 +16,49 @@ */ package se.llbit.chunky.renderer; -public interface RenderConstants { +/** + * The render options contain some values for tweaking the global render performance. + *

Which of these options a specific Renderer/RayTracer supports is not defined. + */ +public interface RenderOptions { /** * Default samples per pixel per pass */ int SPP_PER_PASS_DEFAULT = 1; + int getSppPerPass(); + /** - * Default number of worker threads. - * Is set to the number of available CPU cores. + * Default CPU load */ - int NUM_RENDER_THREADS_DEFAULT = Runtime.getRuntime().availableProcessors(); + int TARGET_CPU_LOAD_PERCENTAGE = 100; /** - * Default CPU load + * Default number of worker threads. + * Is set to the number of available CPU cores. */ - int CPU_LOAD_DEFAULT = 100; + int RENDER_THREAD_COUNT_DEFAULT = Runtime.getRuntime().availableProcessors(); /** * Minimum number of worker threads */ - int NUM_RENDER_THREADS_MIN = 1; + int RENDER_THREAD_COUNT_MIN = 1; /** * Maximum number of worker threads */ - int NUM_RENDER_THREADS_MAX = 10000; + int RENDER_THREAD_COUNT_MAX = 10000; + + /** + * -1 will use all available processors + */ + int getRenderThreadCount(); /** * Default tile width */ int TILE_WIDTH_DEFAULT = 8; + + int getTileWidth(); }