Allow the user to configure the GPU render bucket size (#220)

* Feature: user defined render bucket size when using GPUs
This commit is contained in:
Luis Uguina
2020-05-16 18:20:38 +10:00
committed by GitHub
parent 36e32ab168
commit bd88b2e3dd
9 changed files with 255 additions and 15 deletions

View File

@@ -57,6 +57,7 @@ public class Configuration {
private int priority; private int priority;
private ComputeType computeMethod; private ComputeType computeMethod;
private GPUDevice GPUDevice; private GPUDevice GPUDevice;
private int renderbucketSize;
private boolean detectGPUs; private boolean detectGPUs;
private boolean printLog; private boolean printLog;
private List<Pair<Calendar, Calendar>> requestTime; private List<Pair<Calendar, Calendar>> requestTime;
@@ -80,6 +81,7 @@ public class Configuration {
this.priority = 19; // default lowest this.priority = 19; // default lowest
this.computeMethod = null; this.computeMethod = null;
this.GPUDevice = null; this.GPUDevice = null;
this.renderbucketSize = -1;
this.userHasSpecifiedACacheDir = false; this.userHasSpecifiedACacheDir = false;
this.detectGPUs = true; this.detectGPUs = true;
this.workingDirectory = null; this.workingDirectory = null;

View File

@@ -167,16 +167,24 @@ public class Job {
+ " pass\n" + " pass\n"
+ "signal.signal(signal.SIGINT, hndl)\n"; + "signal.signal(signal.SIGINT, hndl)\n";
if (isUseGPU() && configuration.getGPUDevice() != null && configuration.getComputeMethod() != ComputeType.CPU) { 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 = "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"); gui.setComputeMethod("GPU");
} }
else { else {
// Otherwise (CPU), fix the tile size to 32x32px
core_script = "sheepit_set_compute_device(\"NONE\", \"CPU\", \"CPU\")\n"; 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"); gui.setComputeMethod("CPU");
} }
core_script += ignore_signal_script; 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; File script_file = null;
String command1[] = getRendererCommand().split(" "); String command1[] = getRendererCommand().split(" ");
int size_command = command1.length + 2; // + 2 for script int size_command = command1.length + 2; // + 2 for script

View File

@@ -50,6 +50,7 @@ public class SettingsLoader {
private String hostname; private String hostname;
private String computeMethod; private String computeMethod;
private String gpu; private String gpu;
private String renderbucketSize;
private String cores; private String cores;
private String ram; private String ram;
private String renderTime; 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) { if (path_ == null) {
path = getDefaultFilePath(); path = getDefaultFilePath();
} }
@@ -105,6 +106,10 @@ public class SettingsLoader {
if (gpu_ != null) { if (gpu_ != null) {
gpu = gpu_.getId(); gpu = gpu_.getId();
} }
if (renderbucketSize_ >= 32) {
renderbucketSize = String.valueOf(renderbucketSize_);
}
} }
public static String getDefaultFilePath() { public static String getDefaultFilePath() {
@@ -134,6 +139,10 @@ public class SettingsLoader {
prop.setProperty("compute-gpu", gpu); prop.setProperty("compute-gpu", gpu);
} }
if (renderbucketSize != null) {
prop.setProperty("renderbucket-size", renderbucketSize);
}
if (cores != null) { if (cores != null) {
prop.setProperty("cores", cores); prop.setProperty("cores", cores);
} }
@@ -213,6 +222,7 @@ public class SettingsLoader {
this.hostname = null; this.hostname = null;
this.computeMethod = null; this.computeMethod = null;
this.gpu = null; this.gpu = null;
this.renderbucketSize = null;
this.cacheDir = null; this.cacheDir = null;
this.autoSignIn = null; this.autoSignIn = null;
this.ui = null; this.ui = null;
@@ -243,6 +253,10 @@ public class SettingsLoader {
this.gpu = prop.getProperty("compute-gpu"); this.gpu = prop.getProperty("compute-gpu");
} }
if (prop.containsKey("renderbucket-size")) {
this.renderbucketSize = prop.getProperty("renderbucket-size");
}
if (prop.containsKey("cpu-cores")) { // backward compatibility if (prop.containsKey("cpu-cores")) { // backward compatibility
this.cores = prop.getProperty("cpu-cores"); this.cores = prop.getProperty("cpu-cores");
} }
@@ -348,8 +362,27 @@ public class SettingsLoader {
GPUDevice device = GPU.getGPUDevice(gpu); GPUDevice device = GPU.getGPUDevice(gpu);
if (device != null) { if (device != null) {
config.setGPUDevice(device); 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) { if (config.getNbCores() == -1 && cores != null) {
config.setNbCores(Integer.valueOf(cores)); config.setNbCores(Integer.valueOf(cores));
} }
@@ -383,6 +416,6 @@ public class SettingsLoader {
@Override @Override
public String toString() { 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 + "]";
} }
} }

View File

@@ -19,10 +19,14 @@
package com.sheepit.client.hardware.gpu; 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 { public class GPUDevice {
private String type; private String type;
private String model; private String model;
private long memory; // in B private long memory; // in B
private int renderBucketSize;
private String id; private String id;
@@ -33,6 +37,7 @@ public class GPUDevice {
this.model = model; this.model = model;
this.memory = ram; this.memory = ram;
this.id = id; this.id = id;
this.renderBucketSize = 32;
} }
public GPUDevice(String type, String model, long ram, String id, String oldId) { public GPUDevice(String type, String model, long ram, String id, String oldId) {
@@ -80,8 +85,43 @@ public class GPUDevice {
this.oldId = id; 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 @Override
public String toString() { 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 + "]";
} }
} }

View File

@@ -4,4 +4,8 @@ import java.util.List;
public interface GPULister { public interface GPULister {
public abstract List<GPUDevice> getGpus(); public abstract List<GPUDevice> getGpus();
public abstract int getRecommendedRenderBucketSize(long memory);
public abstract int getMaximumRenderBucketSize(long memory);
} }

View File

@@ -127,4 +127,14 @@ public class Nvidia implements GPULister {
return devices; 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;
}
} }

View File

@@ -123,6 +123,17 @@ public class OpenCL implements GPULister {
return available_devices; 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) { private static String getInfodeviceString(OpenCLLib lib, CLDeviceId.ByReference device, int type) {
byte name[] = new byte[256]; byte name[] = new byte[256];

View File

@@ -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) @Option(name = "-theme", usage = "Specify the theme to use for the graphical client, default 'light', available 'light', 'dark'", required = false)
private String theme = null; 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) { public static void main(String[] args) {
new Worker().doMain(args); new Worker().doMain(args);
@@ -278,6 +281,12 @@ public class Worker {
config.setComputeMethod(compute_method); 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) { if (ui_type != null) {
config.setUIType(ui_type); config.setUIType(ui_type);
} }

View File

@@ -62,6 +62,9 @@ import com.sheepit.client.SettingsLoader;
import com.sheepit.client.hardware.cpu.CPU; import com.sheepit.client.hardware.cpu.CPU;
import com.sheepit.client.hardware.gpu.GPU; import com.sheepit.client.hardware.gpu.GPU;
import com.sheepit.client.hardware.gpu.GPUDevice; 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.network.Proxy;
import com.sheepit.client.os.OS; import com.sheepit.client.os.OS;
import com.sheepit.client.standalone.GuiSwing; import com.sheepit.client.standalone.GuiSwing;
@@ -79,6 +82,8 @@ public class Settings implements Activity {
private JFileChooser cacheDirChooser; private JFileChooser cacheDirChooser;
private JCheckBox useCPU; private JCheckBox useCPU;
private List<JCheckBoxGPU> useGPUs; private List<JCheckBoxGPU> useGPUs;
private JLabel renderbucketSizeLabel;
private JSlider renderbucketSize;
private JSlider cpuCores; private JSlider cpuCores;
private JSlider ram; private JSlider ram;
private JSpinner renderTime; private JSpinner renderTime;
@@ -111,6 +116,7 @@ public class Settings implements Activity {
applyTheme(config.getTheme()); // apply the proper theme (light/dark) applyTheme(config.getTheme()); // apply the proper theme (light/dark)
List<GPUDevice> gpus = GPU.listDevices(config); List<GPUDevice> gpus = GPU.listDevices(config);
useGPUs.clear(); // Empty the auxiliary list (used in the list of checkboxes)
GridBagConstraints constraints = new GridBagConstraints(); GridBagConstraints constraints = new GridBagConstraints();
int currentRow = 0; int currentRow = 0;
@@ -252,21 +258,74 @@ public class Settings implements Activity {
gridbag.setConstraints(useCPU, compute_devices_constraints); gridbag.setConstraints(useCPU, compute_devices_constraints);
compute_devices_panel.add(useCPU); compute_devices_panel.add(useCPU);
for (GPUDevice gpu : gpus) { if (gpus.size() > 0) {
JCheckBoxGPU gpuCheckBox = new JCheckBoxGPU(gpu); renderbucketSizeLabel = new JLabel("Renderbucket size:");
gpuCheckBox.setToolTipText(gpu.getId()); renderbucketSize = new JSlider();
if (gpuChecked) { renderbucketSize.setMajorTickSpacing(1);
GPUDevice config_gpu = config.getGPUDevice(); renderbucketSize.setMinorTickSpacing(1);
if (config_gpu != null && config_gpu.getId().equals(gpu.getId())) { renderbucketSize.setPaintTicks(true);
gpuCheckBox.setSelected(gpuChecked); 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++; compute_devices_constraints.gridy++;
gridbag.setConstraints(gpuCheckBox, compute_devices_constraints);
compute_devices_panel.add(gpuCheckBox); gridbag.setConstraints(renderbucketSizeLabel, compute_devices_constraints);
useGPUs.add(gpuCheckBox); 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(); CPU cpu = new CPU();
@@ -297,6 +356,7 @@ public class Settings implements Activity {
compute_devices_constraints.weightx = 1.0; compute_devices_constraints.weightx = 1.0;
gridbag.setConstraints(cpuCores, compute_devices_constraints); 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); compute_devices_panel.add(cpuCores);
} }
@@ -332,6 +392,7 @@ public class Settings implements Activity {
compute_devices_constraints.weightx = 1.0; compute_devices_constraints.weightx = 1.0;
gridbag.setConstraints(ram, compute_devices_constraints); 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); compute_devices_panel.add(ram);
parent.getContentPane().add(compute_devices_panel, constraints); parent.getContentPane().add(compute_devices_panel, constraints);
@@ -446,6 +507,29 @@ public class Settings implements Activity {
} }
} }
private void buildRenderBucketSizeSlider(int maxRenderbucketSize, int selectedBucketSize) {
Hashtable<Integer, JLabel> renderbucketSizeTable = new Hashtable<Integer, JLabel>();
// 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() { public boolean checkDisplaySaveButton() {
boolean selected = useCPU.isSelected(); boolean selected = useCPU.isSelected();
for (JCheckBoxGPU box : useGPUs) { for (JCheckBoxGPU box : useGPUs) {
@@ -503,10 +587,43 @@ public class Settings implements Activity {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
renderbucketSizeLabel.setVisible(false);
renderbucketSize.setVisible(false);
int counter = 0;
for (JCheckBox box : useGPUs) { 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) { if (box.equals(e.getSource()) == false) {
box.setSelected(false); box.setSelected(false);
} }
counter++;
} }
checkDisplaySaveButton(); checkDisplaySaveButton();
} }
@@ -578,6 +695,11 @@ public class Settings implements Activity {
config.setGPUDevice(selected_gpu); config.setGPUDevice(selected_gpu);
} }
int renderbucket_size = -1;
if (renderbucketSize != null) {
renderbucket_size = (int) Math.pow(2, (renderbucketSize.getValue() + 5));
}
int cpu_cores = -1; int cpu_cores = -1;
if (cpuCores != null) { if (cpuCores != null) {
cpu_cores = cpuCores.getValue(); cpu_cores = cpuCores.getValue();
@@ -638,6 +760,7 @@ public class Settings implements Activity {
hostnameText, hostnameText,
method, method,
selected_gpu, selected_gpu,
renderbucket_size,
cpu_cores, cpu_cores,
max_ram, max_ram,
max_rendertime, max_rendertime,