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 ComputeType computeMethod;
private GPUDevice GPUDevice;
private int renderbucketSize;
private boolean detectGPUs;
private boolean printLog;
private List<Pair<Calendar, Calendar>> 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;

View File

@@ -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

View File

@@ -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 + "]";
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -4,4 +4,8 @@ import java.util.List;
public interface GPULister {
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;
}
@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;
}
@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];

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)
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);
}

View File

@@ -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<JCheckBoxGPU> 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<GPUDevice> 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<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() {
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,