diff --git a/src/com/sheepit/client/Configuration.java b/src/com/sheepit/client/Configuration.java index 06a3668..9c3cf6a 100644 --- a/src/com/sheepit/client/Configuration.java +++ b/src/com/sheepit/client/Configuration.java @@ -57,6 +57,7 @@ public class Configuration { private int priority; private ComputeType computeMethod; private GPUDevice GPUDevice; + private int renderbucketSize; private boolean detectGPUs; private boolean printLog; private List> requestTime; @@ -80,6 +81,7 @@ public class Configuration { this.priority = 19; // default lowest this.computeMethod = null; this.GPUDevice = null; + this.renderbucketSize = -1; this.userHasSpecifiedACacheDir = false; this.detectGPUs = true; this.workingDirectory = null; diff --git a/src/com/sheepit/client/Job.java b/src/com/sheepit/client/Job.java index 7728443..e6eb7f2 100644 --- a/src/com/sheepit/client/Job.java +++ b/src/com/sheepit/client/Job.java @@ -167,16 +167,24 @@ public class Job { + " pass\n" + "signal.signal(signal.SIGINT, hndl)\n"; if (isUseGPU() && configuration.getGPUDevice() != null && configuration.getComputeMethod() != ComputeType.CPU) { + // If using a GPU, check the proper tile size + int tileSize = configuration.getGPUDevice().getRenderbucketSize(); + core_script = "sheepit_set_compute_device(\"" + configuration.getGPUDevice().getType() + "\", \"GPU\", \"" + configuration.getGPUDevice().getId() + "\")\n"; + core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", + tileSize); + + log.debug(String.format("Rendering bucket size set to %1$dx%1$d pixels", tileSize)); gui.setComputeMethod("GPU"); } else { + // Otherwise (CPU), fix the tile size to 32x32px core_script = "sheepit_set_compute_device(\"NONE\", \"CPU\", \"CPU\")\n"; + core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", 32); gui.setComputeMethod("CPU"); } core_script += ignore_signal_script; - core_script += String.format("bpy.context.scene.render.tile_x = %1$d\nbpy.context.scene.render.tile_y = %1$d\n", 32); File script_file = null; String command1[] = getRendererCommand().split(" "); int size_command = command1.length + 2; // + 2 for script diff --git a/src/com/sheepit/client/SettingsLoader.java b/src/com/sheepit/client/SettingsLoader.java index 25df28d..030c222 100644 --- a/src/com/sheepit/client/SettingsLoader.java +++ b/src/com/sheepit/client/SettingsLoader.java @@ -50,6 +50,7 @@ public class SettingsLoader { private String hostname; private String computeMethod; private String gpu; + private String renderbucketSize; private String cores; private String ram; private String renderTime; @@ -68,7 +69,7 @@ public class SettingsLoader { } } - public SettingsLoader(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_, int cores_, long maxRam_, int maxRenderTime_, String cacheDir_, boolean autoSignIn_, String ui_, String theme_, int priority_) { + public SettingsLoader(String path_, String login_, String password_, String proxy_, String hostname_, ComputeType computeMethod_, GPUDevice gpu_, int renderbucketSize_, int cores_, long maxRam_, int maxRenderTime_, String cacheDir_, boolean autoSignIn_, String ui_, String theme_, int priority_) { if (path_ == null) { path = getDefaultFilePath(); } @@ -105,6 +106,10 @@ public class SettingsLoader { if (gpu_ != null) { gpu = gpu_.getId(); } + + if (renderbucketSize_ >= 32) { + renderbucketSize = String.valueOf(renderbucketSize_); + } } public static String getDefaultFilePath() { @@ -134,6 +139,10 @@ public class SettingsLoader { prop.setProperty("compute-gpu", gpu); } + if (renderbucketSize != null) { + prop.setProperty("renderbucket-size", renderbucketSize); + } + if (cores != null) { prop.setProperty("cores", cores); } @@ -213,6 +222,7 @@ public class SettingsLoader { this.hostname = null; this.computeMethod = null; this.gpu = null; + this.renderbucketSize = null; this.cacheDir = null; this.autoSignIn = null; this.ui = null; @@ -243,6 +253,10 @@ public class SettingsLoader { this.gpu = prop.getProperty("compute-gpu"); } + if (prop.containsKey("renderbucket-size")) { + this.renderbucketSize = prop.getProperty("renderbucket-size"); + } + if (prop.containsKey("cpu-cores")) { // backward compatibility this.cores = prop.getProperty("cpu-cores"); } @@ -348,8 +362,27 @@ public class SettingsLoader { GPUDevice device = GPU.getGPUDevice(gpu); if (device != null) { config.setGPUDevice(device); + + // If the user has indicated a render bucket size at least 32x32 px, overwrite the config file value + if (config.getRenderbucketSize() >= 32) { + config.getGPUDevice().setRenderbucketSize(config.getRenderbucketSize()); // Update size + } + else { + // If the configuration file does have any value + if (renderbucketSize != null) { + config.getGPUDevice().setRenderbucketSize(Integer.valueOf(renderbucketSize)); + } + else { + // Don't do anything here as the GPU get's a default value when it's initialised + // The configuration will take the default GPU value + } + } + + // And now update the client configuration with the new value + config.setRenderbucketSize(config.getGPUDevice().getRenderbucketSize()); } } + if (config.getNbCores() == -1 && cores != null) { config.setNbCores(Integer.valueOf(cores)); } @@ -383,6 +416,6 @@ public class SettingsLoader { @Override public String toString() { - return "SettingsLoader [path=" + path + ", login=" + login + ", password=" + password + ", computeMethod=" + computeMethod + ", gpu=" + gpu + ", cacheDir=" + cacheDir + ", theme=" + theme + ", priority="+priority+"]"; + return "SettingsLoader [path=" + path + ", login=" + login + ", password=" + password + ", computeMethod=" + computeMethod + ", gpu=" + gpu + ", renderbucket-size=" + renderbucketSize + ", cacheDir=" + cacheDir + ", theme=" + theme + ", priority=" + priority + "]"; } } diff --git a/src/com/sheepit/client/hardware/gpu/GPUDevice.java b/src/com/sheepit/client/hardware/gpu/GPUDevice.java index c46f68d..4624916 100644 --- a/src/com/sheepit/client/hardware/gpu/GPUDevice.java +++ b/src/com/sheepit/client/hardware/gpu/GPUDevice.java @@ -19,10 +19,14 @@ package com.sheepit.client.hardware.gpu; +import com.sheepit.client.hardware.gpu.nvidia.Nvidia; +import com.sheepit.client.hardware.gpu.opencl.OpenCL; + public class GPUDevice { private String type; private String model; private long memory; // in B + private int renderBucketSize; private String id; @@ -33,6 +37,7 @@ public class GPUDevice { this.model = model; this.memory = ram; this.id = id; + this.renderBucketSize = 32; } public GPUDevice(String type, String model, long ram, String id, String oldId) { @@ -80,8 +85,43 @@ public class GPUDevice { this.oldId = id; } + public int getRenderbucketSize() { + return this.renderBucketSize; + } + + public void setRenderbucketSize(int proposedRenderbucketSize) { + int renderBucketSize = 32; + GPULister gpu; + + if (type.equals("CUDA")) { + gpu = new Nvidia(); + } + else if (type.equals("OPENCL")) { + gpu = new OpenCL(); + } + else { + // If execution takes this branch is because we weren't able to detect the proper GPU technology or + // because is a new one (different from CUDA and OPENCL). In that case, move into the safest position + // of 32x32 pixel tile sizes + System.out.println("GPUDevice::setRenderbucketSize Unable to detect GPU technology. Render bucket size set to 32x32 pixels"); + this.renderBucketSize = 32; + return; + } + + if (proposedRenderbucketSize >= 32) { + if (proposedRenderbucketSize <= gpu.getMaximumRenderBucketSize(getMemory())) { + renderBucketSize = proposedRenderbucketSize; + } + else { + renderBucketSize = gpu.getRecommendedRenderBucketSize(getMemory()); + } + } + + this.renderBucketSize = renderBucketSize; + } + @Override public String toString() { - return "GPUDevice [type=" + type + ", model='" + model + "', memory=" + memory + ", id=" + id + "]"; + return "GPUDevice [type=" + type + ", model='" + model + "', memory=" + memory + ", id=" + id + ", renderbucketSize=" + renderBucketSize + "]"; } } diff --git a/src/com/sheepit/client/hardware/gpu/GPULister.java b/src/com/sheepit/client/hardware/gpu/GPULister.java index 8ef034c..9aecafa 100644 --- a/src/com/sheepit/client/hardware/gpu/GPULister.java +++ b/src/com/sheepit/client/hardware/gpu/GPULister.java @@ -4,4 +4,8 @@ import java.util.List; public interface GPULister { public abstract List getGpus(); + + public abstract int getRecommendedRenderBucketSize(long memory); + + public abstract int getMaximumRenderBucketSize(long memory); } diff --git a/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java b/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java index 383c22e..de388f0 100644 --- a/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java +++ b/src/com/sheepit/client/hardware/gpu/nvidia/Nvidia.java @@ -127,4 +127,14 @@ public class Nvidia implements GPULister { return devices; } + @Override + public int getRecommendedRenderBucketSize(long memory) { + // Optimal CUDA-based GPUs Renderbucket algorithm + return (memory > 1073741824L) ? 256 : 128; + } + + @Override + public int getMaximumRenderBucketSize(long memory) { + return (memory > 1073741824L) ? 512 : 128; + } } diff --git a/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java b/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java index bd8345b..e5ab91d 100644 --- a/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java +++ b/src/com/sheepit/client/hardware/gpu/opencl/OpenCL.java @@ -123,6 +123,17 @@ public class OpenCL implements GPULister { return available_devices; } + @Override + public int getRecommendedRenderBucketSize(long memory) { + // Optimal CUDA-based GPUs Renderbucket algorithm + return (memory > 1073741824L) ? 256 : 128; + } + + @Override + public int getMaximumRenderBucketSize(long memory) { + return (memory > 1073741824L) ? 2048 : 128; + } + private static String getInfodeviceString(OpenCLLib lib, CLDeviceId.ByReference device, int type) { byte name[] = new byte[256]; diff --git a/src/com/sheepit/client/standalone/Worker.java b/src/com/sheepit/client/standalone/Worker.java index b168e42..df71ded 100644 --- a/src/com/sheepit/client/standalone/Worker.java +++ b/src/com/sheepit/client/standalone/Worker.java @@ -111,6 +111,9 @@ public class Worker { @Option(name = "-theme", usage = "Specify the theme to use for the graphical client, default 'light', available 'light', 'dark'", required = false) private String theme = null; + + @Option(name = "-renderbucket-size", usage = "Set a custom GPU renderbucket size (32 for 32x32px, 64 for 64x64px, and so on). NVIDIA GPUs support a maximum renderbucket size of 512x512 pixel, while AMD GPUs support a maximum 2048x2048 pixel renderbucket size. Minimum renderbucket size is 32 pixels for all GPUs", required = false) + private int renderbucketSize = -1; public static void main(String[] args) { new Worker().doMain(args); @@ -278,6 +281,12 @@ public class Worker { config.setComputeMethod(compute_method); + // Change the default configuration if the user has specified a minimum renderbucket size of 32 + if (renderbucketSize >= 32) { + // Send the proposed renderbucket size and check if viable + config.setRenderbucketSize(renderbucketSize); + } + if (ui_type != null) { config.setUIType(ui_type); } diff --git a/src/com/sheepit/client/standalone/swing/activity/Settings.java b/src/com/sheepit/client/standalone/swing/activity/Settings.java index 42bdf00..0aba492 100644 --- a/src/com/sheepit/client/standalone/swing/activity/Settings.java +++ b/src/com/sheepit/client/standalone/swing/activity/Settings.java @@ -62,6 +62,9 @@ import com.sheepit.client.SettingsLoader; import com.sheepit.client.hardware.cpu.CPU; import com.sheepit.client.hardware.gpu.GPU; import com.sheepit.client.hardware.gpu.GPUDevice; +import com.sheepit.client.hardware.gpu.GPULister; +import com.sheepit.client.hardware.gpu.nvidia.Nvidia; +import com.sheepit.client.hardware.gpu.opencl.OpenCL; import com.sheepit.client.network.Proxy; import com.sheepit.client.os.OS; import com.sheepit.client.standalone.GuiSwing; @@ -79,6 +82,8 @@ public class Settings implements Activity { private JFileChooser cacheDirChooser; private JCheckBox useCPU; private List useGPUs; + private JLabel renderbucketSizeLabel; + private JSlider renderbucketSize; private JSlider cpuCores; private JSlider ram; private JSpinner renderTime; @@ -111,6 +116,7 @@ public class Settings implements Activity { applyTheme(config.getTheme()); // apply the proper theme (light/dark) List gpus = GPU.listDevices(config); + useGPUs.clear(); // Empty the auxiliary list (used in the list of checkboxes) GridBagConstraints constraints = new GridBagConstraints(); int currentRow = 0; @@ -252,21 +258,74 @@ public class Settings implements Activity { gridbag.setConstraints(useCPU, compute_devices_constraints); compute_devices_panel.add(useCPU); - for (GPUDevice gpu : gpus) { - JCheckBoxGPU gpuCheckBox = new JCheckBoxGPU(gpu); - gpuCheckBox.setToolTipText(gpu.getId()); - if (gpuChecked) { - GPUDevice config_gpu = config.getGPUDevice(); - if (config_gpu != null && config_gpu.getId().equals(gpu.getId())) { - gpuCheckBox.setSelected(gpuChecked); + if (gpus.size() > 0) { + renderbucketSizeLabel = new JLabel("Renderbucket size:"); + renderbucketSize = new JSlider(); + renderbucketSize.setMajorTickSpacing(1); + renderbucketSize.setMinorTickSpacing(1); + renderbucketSize.setPaintTicks(true); + renderbucketSize.setPaintLabels(true); + + renderbucketSizeLabel.setVisible(false); + renderbucketSize.setVisible(false); + + for (GPUDevice gpu : gpus) { + JCheckBoxGPU gpuCheckBox = new JCheckBoxGPU(gpu); + gpuCheckBox.setToolTipText(gpu.getId()); + if (gpuChecked) { + GPUDevice config_gpu = config.getGPUDevice(); + if (config_gpu != null && config_gpu.getId().equals(gpu.getId())) { + gpuCheckBox.setSelected(gpuChecked); + renderbucketSizeLabel.setVisible(true); + renderbucketSize.setVisible(true); + } + } + gpuCheckBox.addActionListener(new GpuChangeAction()); + + compute_devices_constraints.gridy++; + gridbag.setConstraints(gpuCheckBox, compute_devices_constraints); + compute_devices_panel.add(gpuCheckBox); + useGPUs.add(gpuCheckBox); + } + + // Initialisation values will apply if we are not able to detect the proper GPU technology or + // because is a new one (different from CUDA and OPENCL). In that case, move into a safe position + // of 32x32 pixel render bucket and a maximum of 128x128 pixel for the "unknown GPU" + int maxRenderbucketSize = 128; + int recommendedBucketSize = 32; + + if (config.getComputeMethod() == ComputeType.GPU || config.getComputeMethod() == ComputeType.CPU_GPU) { + GPULister gpu; + + if (config.getGPUDevice().getType().equals("CUDA")) { + gpu = new Nvidia(); + maxRenderbucketSize = gpu.getMaximumRenderBucketSize(config.getGPUDevice().getMemory()); + recommendedBucketSize = gpu.getRecommendedRenderBucketSize(config.getGPUDevice().getMemory()); + } + else if (config.getGPUDevice().getType().equals("OPENCL")) { + gpu = new OpenCL(); + maxRenderbucketSize = gpu.getMaximumRenderBucketSize(config.getGPUDevice().getMemory()); + recommendedBucketSize = gpu.getRecommendedRenderBucketSize(config.getGPUDevice().getMemory()); } } - gpuCheckBox.addActionListener(new GpuChangeAction()); + + buildRenderBucketSizeSlider(maxRenderbucketSize, config.getRenderbucketSize() != -1 ? + ((int) (Math.log(config.getRenderbucketSize()) / Math.log(2))) - 5 : + ((int) (Math.log(recommendedBucketSize) / Math.log(2))) - 5); + compute_devices_constraints.weightx = 1.0 / gpus.size(); + compute_devices_constraints.gridx = 0; compute_devices_constraints.gridy++; - gridbag.setConstraints(gpuCheckBox, compute_devices_constraints); - compute_devices_panel.add(gpuCheckBox); - useGPUs.add(gpuCheckBox); + + gridbag.setConstraints(renderbucketSizeLabel, compute_devices_constraints); + compute_devices_panel.add(renderbucketSizeLabel); + + compute_devices_constraints.gridx = 1; + compute_devices_constraints.weightx = 1.0; + + gridbag.setConstraints(renderbucketSize, compute_devices_constraints); + compute_devices_panel.add(new JLabel(" "), compute_devices_constraints); // Add a space between lines + compute_devices_panel.add(renderbucketSize); } CPU cpu = new CPU(); @@ -297,6 +356,7 @@ public class Settings implements Activity { compute_devices_constraints.weightx = 1.0; gridbag.setConstraints(cpuCores, compute_devices_constraints); + compute_devices_panel.add(new JLabel(" "), compute_devices_constraints); // Add a space between lines compute_devices_panel.add(cpuCores); } @@ -332,6 +392,7 @@ public class Settings implements Activity { compute_devices_constraints.weightx = 1.0; gridbag.setConstraints(ram, compute_devices_constraints); + compute_devices_panel.add(new JLabel(" "), compute_devices_constraints); // Add a space between lines compute_devices_panel.add(ram); parent.getContentPane().add(compute_devices_panel, constraints); @@ -446,6 +507,29 @@ public class Settings implements Activity { } } + private void buildRenderBucketSizeSlider(int maxRenderbucketSize, int selectedBucketSize) { + Hashtable renderbucketSizeTable = new Hashtable(); + + // We "take logs" to calculate the exponent to fill the slider. The logarithm, or log, of a number reflects + // what power you need to raise a certain base to in order to get that number. In this case, as we are + // offering increments of 2^n, the formula will be: + // + // log(tile size in px) + // exponent = -------------------- + // log(2) + // + int steps = (int) (Math.log(maxRenderbucketSize) / Math.log(2)); + + for (int i = 5; i <= steps; i++) { + renderbucketSizeTable.put((i - 5), new JLabel(String.format("%.0f", Math.pow(2, i)))); + } + + renderbucketSize.setMinimum(0); + renderbucketSize.setMaximum(renderbucketSizeTable.size() - 1); + renderbucketSize.setLabelTable(renderbucketSizeTable); + renderbucketSize.setValue(selectedBucketSize); + } + public boolean checkDisplaySaveButton() { boolean selected = useCPU.isSelected(); for (JCheckBoxGPU box : useGPUs) { @@ -503,10 +587,43 @@ public class Settings implements Activity { @Override public void actionPerformed(ActionEvent e) { + renderbucketSizeLabel.setVisible(false); + renderbucketSize.setVisible(false); + + int counter = 0; for (JCheckBox box : useGPUs) { + if (!box.isSelected()) { + box.setSelected(false); + } + else { + GPULister gpu; + int maxRenderbucketSize = 128; // Max default render bucket size + int recommendedBucketSize = 32; // Default recommended render bucket size + + if (useGPUs.get(counter).getGPUDevice().getType().equals("CUDA")) { + gpu = new Nvidia(); + maxRenderbucketSize = gpu.getMaximumRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory()); + recommendedBucketSize = gpu.getRecommendedRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory()); + } + else if (useGPUs.get(counter).getGPUDevice().getType().equals("OPENCL")) { + gpu = new OpenCL(); + maxRenderbucketSize = gpu.getMaximumRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory()); + recommendedBucketSize = gpu.getRecommendedRenderBucketSize(useGPUs.get(counter).getGPUDevice().getMemory()); + } + + buildRenderBucketSizeSlider(maxRenderbucketSize, ((int) (Math.log(recommendedBucketSize) / Math.log(2))) - 5); + + renderbucketSizeLabel.setVisible(true); + renderbucketSize.setVisible(true); + } + + // Simulate a radio button behavior with check buttons while only 1 GPU + // can be selected at a time if (box.equals(e.getSource()) == false) { box.setSelected(false); } + + counter++; } checkDisplaySaveButton(); } @@ -578,6 +695,11 @@ public class Settings implements Activity { config.setGPUDevice(selected_gpu); } + int renderbucket_size = -1; + if (renderbucketSize != null) { + renderbucket_size = (int) Math.pow(2, (renderbucketSize.getValue() + 5)); + } + int cpu_cores = -1; if (cpuCores != null) { cpu_cores = cpuCores.getValue(); @@ -638,6 +760,7 @@ public class Settings implements Activity { hostnameText, method, selected_gpu, + renderbucket_size, cpu_cores, max_ram, max_rendertime,