Skip to content

Commit 34afc3d

Browse files
authored
Add CUDA acceleration (#933)
* Add CUDA support * Add Socket.flagChanged() No more socket.setValue(socket.getValue()) hacks * Allow CUDA versions to be optionally specified with -Pcuda Or -PWITH_CUDA * Add special CUDA socket Socket is disabled when CUDA is unavailable * Exit when CUDA runtime is required but not available * Make normalize operation CUDA-accelerated Slight speedup versus CPU * Add CUDA acceleration to basic CV operations * Clean up flagChanged in OutputSocketImpl * Append 'cuda' to versions of GRIP with CUDA acceleration * Add CUDA classes to dedicated cuda module This lets us create CudaDetectors etc. before loading OpenCV in the core module Move logger setup to its own class, since the core module may not load before app exits * Make SafeShutdown take an ExitCode enum instead of raw int * Use JNI loading to locate CUDA install * Use WriteProperties task to save CUDA runtime properties
1 parent 142b1de commit 34afc3d

File tree

103 files changed

+2413
-506
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+2413
-506
lines changed

core/core.gradle.kts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2+
import com.google.common.io.Files
3+
import java.nio.charset.StandardCharsets
24

35
plugins {
46
id("java")
@@ -15,15 +17,21 @@ application {
1517
val os = osdetector.classifier.replace("osx", "macosx").replace("x86_32", "x86")
1618
val arch = osdetector.arch.replace("x86_64", "x64")
1719

20+
val withCuda = project.hasProperty("cuda") || project.hasProperty("WITH_CUDA")
21+
22+
if (withCuda) {
23+
version = "$version-cuda"
24+
}
25+
1826
dependencies {
1927
api(project(":annotation"))
2028
annotationProcessor(project(":annotation"))
2129
api(group = "com.google.code.findbugs", name = "jsr305", version = "3.0.1")
22-
api(group = "org.bytedeco", name = "javacv", version = "1.1")
23-
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.0.0-1.1")
24-
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.0.0-1.1", classifier = os)
30+
api(group = "org.bytedeco", name = "javacv", version = "1.3")
31+
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.4.3-1.4.3")
32+
api(group = "org.bytedeco.javacpp-presets", name = "opencv", version = "3.4.3-1.4.3", classifier = if (withCuda) "$os-gpu" else os)
2533
api(group = "org.bytedeco.javacpp-presets", name = "videoinput", version = "0.200-1.1", classifier = os)
26-
api(group = "org.bytedeco.javacpp-presets", name = "ffmpeg", version = "0.200-1.1", classifier = os)
34+
api(group = "org.bytedeco.javacpp-presets", name = "ffmpeg", version = "0.200-1.3", classifier = os)
2735
api(group = "org.python", name = "jython", version = "2.7.0")
2836
api(group = "com.thoughtworks.xstream", name = "xstream", version = "1.4.10")
2937
api(group = "org.apache.commons", name = "commons-lang3", version = "3.5")
@@ -59,6 +67,18 @@ tasks.withType<Jar>().configureEach {
5967
}
6068
}
6169

70+
val writeCudaPropertiesTask = tasks.register<WriteProperties>("writeCudaProperties") {
71+
description = "Generates a file to let the GRIP runtime know if its using CUDA-accelerated OpenCV."
72+
outputFile = buildDir.resolve("resources/main/edu/wpi/grip/core/CUDA.properties")
73+
comment = "Information about CUDA requirements for the GRIP runtime"
74+
property("edu.wpi.grip.cuda.enabled", withCuda)
75+
property("edu.wpi.grip.cuda.version", "10.0") // opencv-presets 3.4.3-1.4.3 is compiled with CUDA 10.0
76+
}
77+
78+
tasks.withType<JavaCompile>().configureEach {
79+
dependsOn(writeCudaPropertiesTask)
80+
}
81+
6282
tasks.withType<ShadowJar>().configureEach {
6383
/* The icudt54b directory in Jython takes up 9 megabytes and doesn"t seem to do anything useful. */
6484
exclude("org/python/icu/impl/data/icudt54b/")

core/src/main/java/edu/wpi/grip/core/GripCoreModule.java

Lines changed: 18 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package edu.wpi.grip.core;
22

3+
import edu.wpi.grip.core.cuda.AccelerationMode;
4+
import edu.wpi.grip.core.cuda.CudaDetector;
5+
import edu.wpi.grip.core.cuda.CudaVerifier;
6+
import edu.wpi.grip.core.cuda.NullAccelerationMode;
7+
import edu.wpi.grip.core.cuda.NullCudaDetector;
38
import edu.wpi.grip.core.events.EventLogger;
49
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
510
import edu.wpi.grip.core.metrics.BenchmarkRunner;
@@ -23,22 +28,18 @@
2328
import com.google.common.eventbus.EventBus;
2429
import com.google.common.eventbus.SubscriberExceptionContext;
2530
import com.google.inject.AbstractModule;
31+
import com.google.inject.Scopes;
2632
import com.google.inject.TypeLiteral;
2733
import com.google.inject.assistedinject.FactoryModuleBuilder;
2834
import com.google.inject.matcher.Matchers;
35+
import com.google.inject.name.Names;
2936
import com.google.inject.spi.InjectionListener;
3037
import com.google.inject.spi.TypeEncounter;
3138
import com.google.inject.spi.TypeListener;
3239

33-
import java.io.IOException;
34-
import java.util.logging.FileHandler;
35-
import java.util.logging.Handler;
40+
import java.util.Properties;
3641
import java.util.logging.Level;
37-
import java.util.logging.LogManager;
38-
import java.util.logging.LogRecord;
3942
import java.util.logging.Logger;
40-
import java.util.logging.SimpleFormatter;
41-
import java.util.logging.StreamHandler;
4243

4344
import javax.annotation.Nullable;
4445

@@ -53,55 +54,6 @@ public class GripCoreModule extends AbstractModule {
5354

5455
private static final Logger logger = Logger.getLogger(GripCoreModule.class.getName());
5556

56-
// This is in a static initialization block so that we don't create a ton of
57-
// log files when running tests
58-
static {
59-
//Set up the global level logger. This handles IO for all loggers.
60-
final Logger globalLogger = LogManager.getLogManager().getLogger("");
61-
62-
try {
63-
// Remove the default handlers that stream to System.err
64-
for (Handler handler : globalLogger.getHandlers()) {
65-
globalLogger.removeHandler(handler);
66-
}
67-
68-
GripFileManager.GRIP_DIRECTORY.mkdirs();
69-
final Handler fileHandler
70-
= new FileHandler(GripFileManager.GRIP_DIRECTORY.getPath() + "/GRIP.log");
71-
72-
//Set level to handler and logger
73-
fileHandler.setLevel(Level.INFO);
74-
globalLogger.setLevel(Level.INFO);
75-
76-
// We need to stream to System.out instead of System.err
77-
final StreamHandler sh = new StreamHandler(System.out, new SimpleFormatter()) {
78-
79-
@Override
80-
public synchronized void publish(final LogRecord record) {
81-
super.publish(record);
82-
// For some reason this doesn't happen automatically.
83-
// This will ensure we get all of the logs printed to the console immediately
84-
// when running on a remote device.
85-
flush();
86-
}
87-
};
88-
sh.setLevel(Level.CONFIG);
89-
90-
globalLogger.addHandler(sh); // Add stream handler
91-
92-
globalLogger.addHandler(fileHandler); //Add the handler to the global logger
93-
94-
fileHandler.setFormatter(new SimpleFormatter()); //log in text, not xml
95-
96-
globalLogger.config("Configuration done."); //Log that we are done setting up the logger
97-
globalLogger.config("GRIP Version: " + edu.wpi.grip.core.Main.class.getPackage()
98-
.getImplementationVersion());
99-
100-
} catch (IOException exception) { //Something happened setting up file IO
101-
throw new IllegalStateException("Failed to configure the Logger", exception);
102-
}
103-
}
104-
10557
/*
10658
* This class should not be used in tests. Use GRIPCoreTestModule for tests.
10759
*/
@@ -138,6 +90,16 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
13890
bind(ConnectionValidator.class).to(Pipeline.class);
13991
bind(Source.SourceFactory.class).to(Source.SourceFactoryImpl.class);
14092

93+
// Bind CUDA-specific stuff to default values
94+
// These will be overridden by the GripCudaModule at app runtime, but this lets
95+
// automated tests assume CPU-only operation modes
96+
bind(CudaDetector.class).to(NullCudaDetector.class);
97+
bind(AccelerationMode.class).to(NullAccelerationMode.class);
98+
bind(CudaVerifier.class).in(Scopes.SINGLETON);
99+
bind(Properties.class)
100+
.annotatedWith(Names.named("cudaProperties"))
101+
.toInstance(new Properties());
102+
141103
bind(InputSocket.Factory.class).to(InputSocketImpl.FactoryImpl.class);
142104
bind(OutputSocket.Factory.class).to(OutputSocketImpl.FactoryImpl.class);
143105
install(new FactoryModuleBuilder()
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package edu.wpi.grip.core;
2+
3+
import edu.wpi.grip.core.cuda.AccelerationMode;
4+
import edu.wpi.grip.core.cuda.CudaAccelerationMode;
5+
import edu.wpi.grip.core.cuda.CudaDetector;
6+
import edu.wpi.grip.core.cuda.CudaVerifier;
7+
import edu.wpi.grip.core.cuda.LoadingCudaDetector;
8+
import edu.wpi.grip.core.cuda.NullAccelerationMode;
9+
10+
import com.google.inject.AbstractModule;
11+
import com.google.inject.Scopes;
12+
import com.google.inject.name.Names;
13+
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.util.Properties;
17+
import java.util.logging.Level;
18+
import java.util.logging.Logger;
19+
20+
public class GripCudaModule extends AbstractModule {
21+
22+
private static final Logger logger = Logger.getLogger(GripCudaModule.class.getName());
23+
private static final String CUDA_ENABLED_KEY = "edu.wpi.grip.cuda.enabled";
24+
25+
@Override
26+
protected void configure() {
27+
bind(CudaDetector.class).to(LoadingCudaDetector.class);
28+
29+
Properties cudaProperties = getCudaProperties();
30+
bind(Properties.class)
31+
.annotatedWith(Names.named("cudaProperties"))
32+
.toInstance(cudaProperties);
33+
34+
if (Boolean.valueOf(cudaProperties.getProperty(CUDA_ENABLED_KEY, "false"))) {
35+
bind(AccelerationMode.class).to(CudaAccelerationMode.class);
36+
} else {
37+
bind(AccelerationMode.class).to(NullAccelerationMode.class);
38+
}
39+
40+
bind(CudaVerifier.class).in(Scopes.SINGLETON);
41+
}
42+
43+
private Properties getCudaProperties() {
44+
try (InputStream resourceAsStream = getClass().getResourceAsStream("CUDA.properties")) {
45+
Properties cudaProps = new Properties();
46+
cudaProps.load(resourceAsStream);
47+
return cudaProps;
48+
} catch (IOException e) {
49+
logger.log(Level.WARNING, "Could not read CUDA properties", e);
50+
return new Properties();
51+
}
52+
}
53+
54+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package edu.wpi.grip.core;
2+
3+
import java.io.IOException;
4+
import java.util.logging.FileHandler;
5+
import java.util.logging.Handler;
6+
import java.util.logging.Level;
7+
import java.util.logging.LogManager;
8+
import java.util.logging.LogRecord;
9+
import java.util.logging.Logger;
10+
import java.util.logging.SimpleFormatter;
11+
import java.util.logging.StreamHandler;
12+
13+
public final class Loggers {
14+
15+
private Loggers() {
16+
throw new UnsupportedOperationException("This is a utility class!");
17+
}
18+
19+
/**
20+
* Sets up loggers to print to stdout and to ~/GRIP/GRIP.log. This should only be called once
21+
* in the application lifecycle, at startup.
22+
*/
23+
public static void setupLoggers() {
24+
// Set up the global level logger. This handles IO for all loggers.
25+
final Logger globalLogger = LogManager.getLogManager().getLogger("");
26+
27+
try {
28+
// Remove the default handlers that stream to System.err
29+
for (Handler handler : globalLogger.getHandlers()) {
30+
globalLogger.removeHandler(handler);
31+
}
32+
33+
GripFileManager.GRIP_DIRECTORY.mkdirs();
34+
final Handler fileHandler
35+
= new FileHandler(GripFileManager.GRIP_DIRECTORY.getPath() + "/GRIP.log");
36+
37+
//Set level to handler and logger
38+
fileHandler.setLevel(Level.INFO);
39+
globalLogger.setLevel(Level.INFO);
40+
41+
// We need to stream to System.out instead of System.err
42+
final StreamHandler sh = new StreamHandler(System.out, new SimpleFormatter()) {
43+
44+
@Override
45+
public synchronized void publish(final LogRecord record) {
46+
super.publish(record);
47+
// For some reason this doesn't happen automatically.
48+
// This will ensure we get all of the logs printed to the console immediately
49+
// when running on a remote device.
50+
flush();
51+
}
52+
};
53+
sh.setLevel(Level.CONFIG);
54+
55+
globalLogger.addHandler(sh); // Add stream handler
56+
57+
globalLogger.addHandler(fileHandler); //Add the handler to the global logger
58+
59+
fileHandler.setFormatter(new SimpleFormatter()); //log in text, not xml
60+
61+
globalLogger.config("Configuration done."); //Log that we are done setting up the logger
62+
globalLogger.config("GRIP Version: " + edu.wpi.grip.core.Main.class.getPackage()
63+
.getImplementationVersion());
64+
65+
} catch (IOException exception) { //Something happened setting up file IO
66+
throw new IllegalStateException("Failed to configure the Logger", exception);
67+
}
68+
}
69+
70+
}

core/src/main/java/edu/wpi/grip/core/Main.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package edu.wpi.grip.core;
22

3+
import edu.wpi.grip.core.cuda.CudaVerifier;
34
import edu.wpi.grip.core.events.ExceptionClearedEvent;
45
import edu.wpi.grip.core.events.ExceptionEvent;
56
import edu.wpi.grip.core.exception.GripServerException;
@@ -55,8 +56,20 @@ public class Main {
5556
@SuppressWarnings("JavadocMethod")
5657
public static void main(String[] args) throws IOException, InterruptedException {
5758
new CoreCommandLineHelper().parse(args); // Check for help or version before doing anything else
58-
final Injector injector = Guice.createInjector(Modules.override(new GripCoreModule(),
59-
new GripFileModule(), new GripSourcesHardwareModule()).with(new GripNetworkModule()));
59+
Loggers.setupLoggers();
60+
61+
// Verify CUDA before using the core module, since that will cause OpenCV to be loaded,
62+
// which will crash the app if we use CUDA and it's not available
63+
GripCudaModule cudaModule = new GripCudaModule();
64+
CudaVerifier cudaVerifier = Guice.createInjector(cudaModule).getInstance(CudaVerifier.class);
65+
cudaVerifier.verifyCuda();
66+
67+
final Injector injector = Guice.createInjector(
68+
Modules.override(
69+
new GripCoreModule(),
70+
new GripFileModule(),
71+
new GripSourcesHardwareModule()
72+
).with(new GripNetworkModule(), cudaModule));
6073
injector.getInstance(Main.class).start(args);
6174
}
6275

@@ -81,7 +94,7 @@ public void start(String[] args) throws IOException, InterruptedException {
8194
gripServer.start();
8295
} catch (GripServerException e) {
8396
logger.log(Level.SEVERE, "The HTTP server could not be started", e);
84-
SafeShutdown.exit(1);
97+
SafeShutdown.exit(SafeShutdown.ExitCode.HTTP_SERVER_COULD_NOT_START);
8598
}
8699

87100
if (pipelineRunner.state() == Service.State.NEW) {

0 commit comments

Comments
 (0)