OpenGL in anderem Thread ausführen

Da gab es doch mal einen Befehl OGL, aber ich finde den nicht mehr.
Über Google komm ich nur auf nicht-hilfreiche Antworten dass Multithreading bei OpenGL nichts bringt und man es deshalb erst nicht versuchen sollte. Mir fehlt der richtige Google-Suchbegriff und stehe deshalb auf dem Schlauch.

Hintergrund der Geschichte ist, dass Google den Android Entwicklern Multithreading aufzwingen will indem in der Android OGL ES API der OGL Kontext in einem eigenen Thread erstellt wird, und der ruft dann in einer Schleife immer nur eine Methode des übergebenen Listeners render() auf.
Das macht mir meine Programmarchitektur aber schön kaputt.
Deshalb ist der Plan den Thread direkt wieder zu stoppen und OpenGL auf meiner eigenen Thread Architektur laufen zu lassen.

Hehe… glücklicherweise hast du mich und ich hatte vor kurzem das Problem, OGL aus verschiedenen Threads heraus aufzurufen und das ohne zu wissen, ob auf diesen bereits ein OGL-Kontext existiert oder nicht. Ich habe mir dafür dann sowas gebaut:

[code]import static org.lwjgl.opengl.GL11.GL_VENDOR;
import static org.lwjgl.opengl.GL11.glGetString;

import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;

class PbufferMap {
private static final long IDLE_TIMEOUT = 5000;
private static final long IDLE_TIMEOUT_NANOS = IDLE_TIMEOUT * 1000000;
private static final ResourceSet REFERENCE = new ResourceSet(null);
private static final PixelFormat FORMAT = new PixelFormat();
private static final ThreadSet CACHE = new ThreadSet();
private static int cnt;

static boolean aquire(Object ressource) {
	synchronized (CACHE) {
		Thread t = Thread.currentThread();
		ResourceSet r = CACHE.forThread(t);
		if(r == null) {
			r = new ResourceSet(t);
			CACHE.add(r);
		}
		r.add(ressource);
		return r.refresh();
	}
}

static void release(Object ressource) {
	synchronized (CACHE) {
		Thread t = Thread.currentThread();
		ResourceSet r = CACHE.forThread(t);
		if(r != null) {
			r.remove(ressource);
		}
	}
}

private static final class ThreadSet extends IdentityHashMap<ResourceSet, Object> {
	private static final long serialVersionUID = 8331589906165047839L;

	private Thread scheduler;

	ResourceSet forThread(Thread t) {
		synchronized(t) {
			if(scheduler == null) {
				scheduler = new Thread("Pbuffer Dispose Scheduler " + (cnt++)) {
					private TreeSet<ResourceSet> ressources = new TreeSet<ResourceSet>();

					{
						setDaemon(true);
					}

					@Override
					public void run() {
						while(true) {
							synchronized (this) {
								try {
									wait(IDLE_TIMEOUT);
								} catch(InterruptedException e) {
									break;
								}
							}
							REFERENCE.refresh();
							synchronized (CACHE) {
								ressources.addAll(CACHE.keySet());
							}
							Set<ResourceSet> tail = new HashSet<ResourceSet>(ressources.tailSet(REFERENCE, false));
							if(!tail.isEmpty()) {
								for(ResourceSet r : tail) {
									r.destroy();
									ressources.remove(r);
								}
								synchronized (CACHE) {
									CACHE.retainAll(ressources);
									if(CACHE.isEmpty()) {
										break;
									}
								}
							}
						}
						scheduler = null;
					}
				};
				scheduler.start();
			}
			for(ResourceSet r : this.keySet()) {
				if(r.t == t) {
					return r;
				}
			}
			return null;
		}
	}

	void add(ResourceSet r) {
		if(get(r) == null) {
			put(r, Boolean.TRUE);
		}
	}

	void retainAll(Collection<ResourceSet> c) {
		Iterator<ResourceSet> it = keySet().iterator();
		while(it.hasNext()) {
			if(!c.contains(it.next())) {
				it.remove();
			}
		}
	}
}

private static final class ResourceSet extends IdentityHashMap<Object, Object> implements Comparable<ResourceSet> {
	private static final long serialVersionUID = -3207232556780437227L;

	private final Thread t;
	private Pbuffer buffer;
	private long nanos;

	private ResourceSet(Thread t) {
		this.t = t;
	}

	void destroy() {
		if(buffer != null) {
			try {
				buffer.destroy();
				buffer = null;
			} catch(Exception e) {
				// dann halt nicht
			}
		}
	}

	void add(Object ressource) {
		if(get(ressource) == null) {
			put(ressource, Boolean.TRUE);
		}
	}

	boolean refresh() {
		nanos = System.nanoTime();
		if(this != REFERENCE) {
			try {
				glGetString(GL_VENDOR);
				if(buffer != null && !buffer.isCurrent()) {
					buffer.destroy();
					buffer = null;
					return true;
				}
				return false;
			} catch(Throwable e) {
				try {
					if(buffer == null || buffer.isBufferLost()) {
						buffer = new Pbuffer(1, 1, FORMAT, null);
						buffer.makeCurrent();
						return true;
					}
					buffer.makeCurrent();
					return false;
				} catch(LWJGLException ee) {
					throw new RuntimeException("open gl not available");
				}
			}
		}
		nanos -= IDLE_TIMEOUT_NANOS;
		return false;
	}

	@Override
	public int compareTo(ResourceSet o) {
		long n = nanos - o.nanos;
		return (n > 0)? -1 : (n < 0)? 1 : 0;
	}

	@Override
	public String toString() {
		return String.valueOf(nanos);
	}
}

}[/code]
Mit aquire kann man einen OGL-Kontext für den laufenden Thread erstellen und ggf. initialisieren, wenn true zurückgegeben wird. Mit release gibt man den Kontext wieder frei,
release zerstört jedoch den Kontext nicht gleich, sondern wartet 5 Sekunden Inaktivität ab.

Ich denke mal, für den Anfang genügt das so, aber es läßt sich sicher noch verbessern.

1 „Gefällt mir“

Engelsgesang :innocent: :open_mouth: :musical_score: haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Wobei ich sagen muss, dass das echt kompliziert ist. Und dann der Umweg über den Pixelbuffer für jeden Thread?
Das wird teuer auf Android.
Puh. Ich lass das mal als Antwort stehen, suche aber noch weiter nach besseren Möglichkeiten.

Dann mal einen Schritt zurück (da ich keine Ahnung von den Android-GL-spezifika und anderen Dingen habe, mögen die Fragen aber naiv klingen) :

  • In welcher Hinsicht geht deine Programmarchitektur damit kaputt? (Eine “init()”- und eine “render()”-Funktion gibt’s ja doch meistens…)
  • Kann man da überhaupt so einhaken, dass man den Thread selbst festlegen kann? Je nachdem, wie strikt die das zugenagelt haben, könnte da ja zumindest Reflection nötig sein, falls es überhaupt geht…

Richtig. Aber mein portiertes Programm habe ich damals mit Hinsicht auf Multithreading geschrieben. Der Architektur zugrunde - und darauf aufbauend - liegt ein experimenteller etwas anderer Ansatz eines Workerthreadpools (der fast vollständig auf synchronised verzichtet).

Jetzt müsste ich das großflächig umschreiben um Fremd-Erzeugte-Threads dort einzubinden über die ich zudem keine vollständige Kontrolle habe was Erzeugung, Pausieren und Stoppen angeht.

Damals hatte ich mal mit JOGL immer eine Methode aufgerufen um den Thread einfach zu ändern.
Im Moment aber schaue ich mir den SourceCode an um manuell einen Kontext zu erzeugen

OK, mehr Infos könnten da interessant sein. Es gibt ja die http://download.java.net/media/jogl/jogl-2.x-docs/javax/media/opengl/GLContext.html#makeCurrent() -Methodenfamilie (bei LWJGL mit irgendwas um “setCapabilities” herum), aber ich habe mit dieser Art Multithreading in GL noch nicht wirklich was gemacht.