[C++] pthreads/opengl Problem

Unicate

Erfahrenes Mitglied
Hallo alle zusammen!

Ich habe ein merkwürdiges Problem. Ich habe eine OpenGL Umgebung in der ich dynamisch (das ist eine Anforderung) Texturen laden will und dies auch schon kann. Nun dauert das laden auf die Grafikkarte (glTexImage2D) relativ lange drum wollt ich diesen Vorgang als Thread in den Hintergrund legen. Gesagt getan. Um Speicher zu sparen möchte ich alte, nicht mehr verwendete OpenGL Texturen wiederverwenden bzw. alte überschreiben. Problem ist, dass wenn ich das synchron mache funktioniert das. Sobald ich das hochladen in einen Thread auslagere werden immer nur die zuerst geladenen Texturen angezeigt.

Hier mal der Code:
PHP:
void Texture::useTexture(Image *image) {

	// if the opengl texture id already exists
	if(_textureID) {
		// if the texture has been changed
		if(image->getID() != _imageID) {
			_imageID = image->getID();
//			glBindTexture(GL_TEXTURE_2D, _textureID);
//			glTexImage2D(GL_TEXTURE_2D, 0, image->getColorFormat(), image->getWidth(), image->getHeight(), 0, image->getColorFormat(), GL_UNSIGNED_BYTE, image->getPixelData());
			load(image, _textureID, _imageID);
		}
	} else {
		GLuint textureID;
		// Use tightly packed data
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		//  Generate a texture object
		glGenTextures(1, &textureID);
		// Bind the texture object
		glBindTexture(GL_TEXTURE_2D, textureID);

		//  upload the texture
		glTexImage2D(GL_TEXTURE_2D, 0, image->getColorFormat(), image->getWidth(), image->getHeight(), 0, image->getColorFormat(), GL_UNSIGNED_BYTE, image->getPixelData());
		// Set the filtering mode
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

//		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
//		glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
		_textureID = textureID;
	}
}

PHP:
struct texture_t {
	Image *image;
	int textureID;
	int imageID;
};

void createTexture(void *value) {
	pthread_detach(NULL);
	Image *image = ((texture_t*)value)->image;
	// critical section cause of Texture::bind()
	pthread_mutex_lock( &mutex );
	glBindTexture(GL_TEXTURE_2D, ((texture_t*)value)->textureID);
	glTexImage2D(GL_TEXTURE_2D, 0, image->getColorFormat(), image->getWidth(), image->getHeight(), 0, image->getColorFormat(), GL_UNSIGNED_BYTE, image->getPixelData());
	pthread_mutex_unlock( &mutex );
	// end of critical section
	delete (texture_t*)value;
	pthread_exit(NULL);
}

void load(Image *image, int texid, int imageID) {
	pthread_t thread;
	texture_t *tex = new texture_t;
	tex->textureID = texid;
	tex->imageID = imageID;
	tex->image = image;
	pthread_create(&thread, NULL, (void* (*)(void*))createTexture, (void*)tex);
}

Ausgaben vor und in dem Thread haben mir bestätigt, das der Imagepointer und der Pixeldatenpointer auch wirklich die richtigen sind, die in die Funktion eingesetzt werden.

Wenn ich Zeile 8 und 9 einkommentiere dafür die 10 auskommentiere, dann funktioniert das laden der Texturen so wie es soll, nur das der Ladevorgang zu lange blockiert.

Was ist hier falsch?
 
Hallo,

in deinem neu erzeugten Thread ist kein OpenGL-Kontext gesetzt. *glMakeCurrent gilt immer nur für den aktuellen Thread. Du kannst also entweder 1. im neuen Thread deinen OpenGL-Kontext setzen, musst ihn dann aber im Hauptthread deaktivieren, oder 2. im neuen Thread einen eigenen OpenGL-Kontext anlegen und mit *glShareLists mit dem Hauptkontext verbinden.

Das ganze Vorhaben bringt allerdings nur dann was, wenn du während des Ladevorgangs im Hauptthread die CPU beschäftigst. Die OpenGL-Aufrufe werden nämlich spätestens im Treiber wieder serialisiert. Siehe auch http://www.opengl.org/wiki/OpenGL_and_multithreading

Grüße,
Matthias
 
Super, das klingt doch mal nach nem Ansatz.

Letzeres klingt nach dem was ich brauche. Der Hauptthread ist gerade damit beschäftigt etwas zu rendern. Der zweite Thread soll ja "nur" die Texturen für den Hauptthread laden.
 
Das stellt sich als schwieriger raus als gedacht, aber das liegt daran, dass ich vergessen hatte zu erwähnen das es sich um OpenGLES 2.0 handelt und nicht um das normale OpenGL.
Desweiteren wird das eine Android Implementation (was auch erklärt, warum der texture upload so lange dauert)

@Matthias: die funktion glMakeCurrent habe ich zwar zur Verfügung allerdings bringt mir diese nichts, da beim laden der Textur dann der Mainrenderloop verlassen würde, woraus folgt dass dieser nicht weiter rendert.
glShareLists gibt es im gles nicht.

Ich versuche es mal mit den Contexts. Wenn jemand noch eine andere Lösung hat oder eine hilfestellung geben könnte, wär das echt gut.

Danke
 
Hallo Unicate,

mit OpenGL ES habe ich leider keine Erfahrung. Aber egal wie du es auch drehst und wendest: einen Geschwindigkeitsvorteil wirst du durch das Auslagern des Textur-Uploads in einen eigenen Thread (sofern überhaupt möglich) nicht erfahren.

Grüße,
Matthias
 
Ich seh ein, dass die GPU im Moment des hochladens blockiert ist, so dass ich meinen "Ruckler" wohl so nicht heraus bekomme.

Hat jemand von euch vielleicht eine Idee wie ich das machen könnte?
Ich hatte schon überlegt, die schon geladenen Texturen im moment des hochladens nicht zu bewegen. Dann sieht man den Upload nicht. Das find ich aber ein wenig "dreckig", wenn ihr wisst was ich meine. Kann man die Textur evtl in kleinen Portionen schicken, sodass der upload der kleinen Teile nicht sichtbar ist? Wenn ja wie? Ich kenn im Moment nur

PHP:
glTexImage2D(GL_TEXTURE_2D, 0, image->getColorFormat(), image->getWidth(), image->getHeight(), 0, image->getColorFormat(), GL_UNSIGNED_BYTE, image->getPixelData());

und da lädt er die ganze Textur hoch.
 
Super, das geht gut soweit.

Problem hierbei ist die Skalierung. Es sieht so aus, als ob die Texture die ich mit glTexSubImage2D rein lade an die skalierung der vorhergehenden Textur angepasst wird.

Also ich lade zuerst eine textur 1024x1024, danach lade ich per glTexSubImage2D eine 512x512 nach, welche dann nur 1/4 so groß auf dem mesh zu sehen ist. Andersherum misslingt der upload vorgang mit 501-GL_INVALID_VALUE.

Das heisst für mich die nachgeladene Image darf nicht größer sein als die vorhergehende bzw anders herum.

Kann ich das irgendwie umgehen?
 
Nein, vermutlich nicht. glTexImage löscht zuerst den alten Speicherbereich im VRAM und allokiert dann neuen Speicher. Wenn du glTexSubImage verwendest überschreibst du nur Teile des vorher angelegten Speicherbereichs.

Das mit der Skalierung versteh ich nicht ganz: Wenn du einen 512x512 großen Speicherbereich einer 1024x1024 Textur überschreibst solltes du eigentlich eine Textur bekommen die weiterhin 1024x1024 groß ist und irgendwo drinnen ist die neue Textur zu sehen.
 
Ja, verzeihung. Ich habe mich vielleicht etwas ungünstig ausgedrückt. Die alte Textur ist natürlich noch zu sehen, und die "neue" ist dann auf der alten drauf. Das ist das überschreiben.

Kann ich irgendwie dem VRAM einen neuen Speicherbereich alloziieren lassen (und den alten natürlich wieder freigeben) ohne was hinein zu kopieren? Weil ich nehme an, das hier der Kopiervorgang so lange dauert. Wenn ich "nur" den Speicher alloziiere und dann mit SubImage die Textur Stück für Stück hineinkopiere sollte das funktionieren oder?
 
Zurück