Aller au contenu

IBL Progressif — Génération incrémentielle

Ce document décrit l'architecture de génération progressive (incrémentielle par image) de la carte spéculaire préfiltrée IBL.

Problème initial

La génération complète de la carte spéculaire préfiltrée (Prefiltered Specular Map) pour un HDR 4K en une seule passe prenait ~350 ms sur GPU intégré, causant un gel visible de l'application.

Solution : Découpe temporelle

Au lieu de générer tous les niveaux de mipmap en une seule frame, on en génère un ou quelques-uns par image.

Stratégie de découpe

typedef struct {
    int current_mip;      // Niveau actuellement en cours de génération
    int total_mips;       // Nombre total de niveaux
    bool in_progress;     // Génération en cours
} IBLProgressState;

Dans la boucle principale :

if (ibl_progress.in_progress) {
    // Générer 1 niveau par image
    generate_specular_mip(&ibl, ibl_progress.current_mip);
    ibl_progress.current_mip++;

    if (ibl_progress.current_mip >= ibl_progress.total_mips) {
        ibl_progress.in_progress = false;
    }
}

Découpe par tranche Y (u_max_y_slice)

Pour les niveaux de mip larges (mip 0, mip 1), même une seule passe peut dépasser le budget. Le shader accepte un paramètre u_max_y_slice pour traiter seulement une bande horizontale :

// Uniforme de contexte
uniform int u_max_y_slice;

void main() {
    ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
    if (texel.y >= u_max_y_slice) return; // Sauter les texels hors de la tranche
    // ... calcul ...
}

Protection contre le chevauchement

Un mécanisme évite de démarrer une nouvelle génération avant que la précédente soit terminée :

// Vérifier la clôture de la passe précédente
if (ibl.prev_fence != NULL) {
    GLenum status = glClientWaitSync(ibl.prev_fence, 0, 0);
    if (status == GL_TIMEOUT_EXPIRED) return; // Pas encore terminée
    glDeleteSync(ibl.prev_fence);
}

// Lancer la passe suivante
dispatch_mip_pass(...);
ibl.prev_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

Optimisation de la barrière différée

La barrière glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT) n'est émise qu'une seule fois après la génération complète de tous les mips, plutôt qu'après chaque mip :

// Avant (une barrière par mip — sous-optimal)
for (int mip = 0; mip < total_mips; mip++) {
    generate_mip(mip);
    glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT); // ← Trop fréquent
}

// Après (une seule barrière finale)
for (int mip = 0; mip < total_mips; mip++) {
    generate_mip(mip);
}
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT); // ← Une seule fois

Résultats de benchmark

Configuration Gel visible Temps total génération
Génération monolithique ~350 ms 350 ms
Génération progressive (1 mip/frame) 0 ms ~10 frames × 35 ms

Voir aussi