Toon posts:

[C++/OpenGL/Windows]Multithread probleem

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik ben bezig met het ontwikkelen van een 3D-RPG-engine en tot nu toe verliep alles voorspoedig. Ik heb het spel in een Linux omgeving ontwikkeld en heb SDL gebruikt om het portable te houden, omdat ik het zowieso nog naar Windows moest porten. Dat porten heb ik nu dus gedaan, en het was gewoon een kwestie van re-compilen, en alles ging goed op één (zichtbaar) ding na.

Het programma draait prima in Windows (in de zin dat hij niet vast loopt), alleen alle tetures die ik gebruik worden helemaal NIET weergegeven, op de plek waar normaal een texture zou moeten zitten staat nu gewoon een lege, witte quad. Na een tijdje debuggen kwam ik er achter dat als de textures in de hoofd-thread ingeladen werden dat ze dan met glGenTextures 'namen' vanaf 1 aangewezen kregen en dat ze in de andere thread namen vanaf 49 aan gewezen kregen.

Op een of andere manier werkten de 49+ textures niet als ze gerenderd werden, maar de 1+ textures wel, dus als work-a-round heb ik alle textures nog los in de hoofdthread ingeladen, en daarna de 49+ textures vervangen door de andere textures. Alles zag er weer goed uit, maar dit kon natuurlijk niet de goede oplossing zijn, want mijn design hangt er van af dat de textures ingeladen worden vanuit een script, wat weer in die andere thread draait. Voor dit scripting systeem maak ik gebruik van GameMonkey.

Hierna ben ik wat verder gaan onderzoeken, met name met de OpenGL debugger 'gDEBugger'. Dit programma geeft me aan dat de fout wordt veroorzaakt door een thread die probeert te schrijven in een gebied waar hij niet mag schrijven:

code:
1
2
3
4
5
Process event - Exception:
Reason: Access violation
Address: 0x4088af
Details: 
The thread tried to read from or write to a virtual address to which it does not have access


Nu is mijn vraag: waarom mag ik dit soort dingen wél doen in Linux, maar waarom niet in Windows? als ik het in Linux onder Valgrind/GDB draai krijg ik helemaal geen fouten, en Windows-GDB zegt ook dat alles goed verloopt. Ook zou ik graag willen weten wàt ik precies verkeerd doe, en of iemand een oplossing heeft

Nu nog ff een verloop van mijn programma:
code:
1
2
3
4
5
6
7
-main() functie
  -SDL wordt opgezet
  -instantie van class word aangemaakt die instanties van (bijna) alle andere game-classen bevat
  -nieuwe thread wordt opgezet, waar een GameMonkey-VM op loopt. Deze laadt een hoofdscipt
    -In dit script worden interne functies aangeroepen om een level te laden, textures, collisision detection spul etc.
    -dit script zelf raakt ook in een loop: hier worden game-gerelateerde dingen gedaan
  -hier na beland hij in de main-loop: input etc en op het laatst worden alle textures naar het scherm gerenderd.


(het programma geeft een exception tijdens het opzetten van de 2e thread in Windows, maar tezij je het in gDEBugger draait loopt het niet vast, alles loopt gewoon door, maar dan met blanke textures)

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Het zal wel iets zijn dat je er vanuit gaat dat iets bestaat in de main thread, maar dat dat nog niet daadwerkelijk geladen is in de andere thread. Synchronizeer je je data wel? Heb je bv een barrier, of lock oid op het feit dat een texture ingeladen is? Ik denk dat je met een race condition zit...lastig om te vinden.

Verwijderd

Topicstarter
Ik denk niet dat het daar aan ligt, maar aan het feit dat er textures ingeladen worden op een andere thread... ik heb zonet ff dit test programmatje geschreven, en hier uit blijkt dat als een texture in een andere thread probeert in te laden dat je dan een exception krijgt. Het gedrag van dit programma is iets anders dan wat er in mijn engine gebeurt, want bij mijn engine krijgen de textures in een andere thread >altijd< een 'naam' van 49+, en hier blijven zo gewoon het getal wat je ze eerst geven had.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
#include <string>

#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>
#include <SDL/SDL_thread.h>

#ifndef GL_BGR_EXT
#define GL_BGR_EXT 0x80E0
#endif

//#define EXCEPTION_PROOF

int LoadTexture(std::string path)
{
    SDL_Surface *Texture = SDL_LoadBMP(path.c_str());
   
    if (!Texture)
    {
        std::cout << "Waar is mijn texture?\n";
        return -1;
    }

    int TextureName = 123;

    glGenTextures(1, &TextureName);
// de onderstaande functies kunnen niet in een andere thread worden opgeroepen
    glBindTexture(GL_TEXTURE_2D, TextureName);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

    gluBuild2DMipmaps( GL_TEXTURE_2D, 3, Texture->w, Texture->h, GL_BGR_EXT, GL_UNSIGNED_BYTE, Texture->pixels);

    SDL_FreeSurface(Texture);

    std::cout << "Generated name: " << TextureName << "\n";
    return TextureName;
}

int TestThreadFunc(void *dummy)
{
    int TexA = LoadTexture("test.bmp");
    int TexB = LoadTexture("test.bmp");
    int TexC = LoadTexture("test.bmp");
    while(1)
    {
       std::cout << "Looping...\n";
       SDL_Delay(500);
    }
}

int main(int argc, char *argv[])
{
    SDL_Init(SDL_INIT_VIDEO);
    SDL_SetVideoMode(800, 600, 32, SDL_OPENGL|SDL_RESIZABLE);

    SDL_Event game_event;

    int TestTexture = LoadTexture("test.bmp");
#ifndef EXCEPTION_PROOF
    SDL_Thread *TestThreadA = SDL_CreateThread(TestThreadFunc, NULL);
#endif
    glLoadIdentity();

    glOrtho(-10, 10, -10, 10, -1, 1);

    for (bool exit_program = false; exit_program == false;)
    {
        while(SDL_PollEvent(&game_event))
        {
            if (game_event.type == SDL_QUIT)
            {
                exit_program = true;
                break;
            }
        }

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, TestTexture);

        glBegin(GL_QUADS);
            glTexCoord2d(1.0,0.0); glVertex3f( 5,  5,  0);
            glTexCoord2d(1.0,1.0); glVertex3f( 5, -5,  0);
            glTexCoord2d(0.0,1.0); glVertex3f(-5, -5,  0);
            glTexCoord2d(0.0,0.0); glVertex3f(-5,  5,  0);
        glEnd();

        glDisable( GL_TEXTURE_2D );

        SDL_GL_SwapBuffers();
    }
}


Maar als ik alleen textures naar het videogeheugen mag kopieeren in de main-thread, hoe moet ik dit dan oplossen? Waarom mag ik dit dan wel in Linux? of is dit misschien een SDL-threading-bug?

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 28-04 18:15

Tomatoman

Fulltime prutser

Je zou natuurlijk de weg van de minste weerstand kunnen kiezen en het laden van textures altijd in de context van de main thread laten plaatsvinden. Wil je een texture laden in een andere thread, dan synchronize je gewoon met een wrapperfunctie in de main thread. Die wrapper doet niets anders dan een texture laden. Met deze aanpak ondervang je in één klap threadgerelateerde bugs in de Windowsimplementatie van textures en threadgerelateerde bugs in je eigen code. Bovendien kun je het script als vanouds in zijn eigen thread laten lopen.

[ Voor 25% gewijzigd door Tomatoman op 17-09-2005 18:36 ]

Een goede grap mag vrienden kosten.


Verwijderd

Topicstarter
tomatoman schreef op zaterdag 17 september 2005 @ 18:33:
Je zou natuurlijk de weg van de minste weerstand kunnen kiezen en het laden van textures altijd in de context van de main thread laten plaatsvinden. Wil je een texture laden in een andere thread, dan synchronize je gewoon met een wrapperfunctie in de main thread. Die wrapper doet niets anders dan een texture laden. Misschien een beetje onbevredigende aanpak, maar het werkt waarschijnlijk wel.
Ik ben een beetje threading-n00b, dus ik weet niet precies hoe ik dit moet gaan aanpakken... Als je een functie vanuit een andere thread aan roept draait hij vanzelf toch ook in die thread? Hoe zou ik dan de 2 thread kunnen laten communiceren zodat de main-thread hem oproept ipv de side-thread?

Ik kan natuurlijk wel een vage constructie maken met een gedeeld object waar alle textures die ingeladen moeten worden ingevoegd worden, en zosnel de mainthread daar in ziet dat er een texture geladen moet worden de texture laad, texture naam in in voegt, en dan wacht tot de side-thread de nieuw verkregen naam weer uitleest.... dit zal waarschijnlijk wel werken (moet ik natuurlijk wel weer op locks enzo gaan letten...) maar is niet echt de oplossing die ik zoek, laat staan een uitleg waarom het in Linux allemaal wel mag en in Windows niet....

  • Robbbert
  • Registratie: April 2005
  • Laatst online: 01-03 12:58
Kijk eens op deze site en op het forum daarvan:
http://nehe.gamedev.net

In al de tijd dat ik met C++ en OpenGL bezig ben geweest, heb ik daar op de site of op in het forum altijd wel een antwoord kunnen vinden. Op het Gamedev forum zijn mensen actief die erg veel verstand hebben van dit soort dingen en die je misschien wel beter kunnen helpen.

  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 28-04 18:15

Tomatoman

Fulltime prutser

Dit is een stukje basiskennis van threading, dat overigens geen gemakkelijk onderwerp is. Normaliter wordt code uitgevoerd in de context van de thread waarvandaan de code wordt aangeroepen. Bovendien werken threads onafhankelijk van elkaar. Wat je met synchronisatie doet is ervoor zorgen dat 2 threads hun gedrag afstemmen op elkaar. Zo zou je ervoor kunnen zorgen dat ze niet tegelijkertijd hetzelfde stukje code uitvoeren maar dat ze op hun beurt moeten wachten, of dat thread 1 blijft wachten totdat thread 2 een bepaald punt heeft bereikt. Wat in jouw situatie het meest praktisch is, is afhankelijk van hoe je programma in elkaar zit. Zie [rml][ Delphi] Threads en locken[/rml] voor een hoop informatie over threads - objecten zoals critial sections, mutexen en semaforen zijn platformonafhankelijk.

Een goede grap mag vrienden kosten.


Verwijderd

Topicstarter
Bedankt voor de link, maar dit gaat iets te ver voor wat ik nodig heb... van die mutexen en zo wist ik trouwens al, maar denk dat ik toch gewoon ff een (thread-safe 8)) global-object maak waar thread een request kunnen maken om een texture in te laden, waarna de main-thread kijkt naar updates, en als hij een request heeft gevonden hem uitvoert, de texture-naam in het goede stukje geheugen zet en daarna de request verwijderd... wordt denk ik gewoon een linked list a) pointers naar ints die de texture-naam moeten gaan bevatten en b) een string van de texture die geladen moet worden....

Maar goed, eerst ff zien hoe dit uit gaat pakken.... en nog ff vragen op nehe forum (waar ik overens wel al men tutorials al vandaan had ) of ze misschien weten wat er fout is om in een andere thread textures in te laden in een andere thread. Maar iig iedereen bedankt voor jullie hulp!

edit:


Heb geimplementeerd, en alles werkt nu naar behoren. Ook ben ik er achter gekomen waarom hij het in Linux wel deed, maar in Windows niet... Het blijkt zo te zijn dat het aan de 'buggy' drivers van ATI ligt, want ik heb het net nog geprobeerd met mijn nieuwe NVIDIA kaart, en daar loopt hij ook in Linux netjes vast, terwijl alles met de ATI drivers gewoon goed verder ging.

[ Voor 19% gewijzigd door Verwijderd op 18-09-2005 15:15 ]

Pagina: 1