Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

- DIRECTX -

Nous veillons aux respects des droits d'auteur : si par erreur un contenu protégé est disponible sans indication sur ce site, merci de contacter l'administrateur, il sera retiré ou mis à jour dans les plus brefs délais.


ASM Mode Reel

Ce premier tutoriel vous aidera à préparer vos outils pour programmer avec DirectX 11 sous Visual Studio 2010. Vu dans .... http://jeux.developpez.com/tutoriels/directx-11-rastertek

Les deux auteur et traducteur http://www.rastertek.com RasterTek

Auteur RasterTek Traducteur : Jérôme Marsaguet Version PDF Version hors-ligne

  • Introduction
  • Tutoriels

Introduction

Cette sÃ©rie de tutoriels en C++ vous permettra d'apprendre Ã utiliser DirectX 11.

Tutoriels

  • Mise en place de DirectX 11 avec Visual Studio
  • CrÃ©ation du cadre de travail et d'une fenÃªtre
  • Initialisation de DirectX 11
  • Tampons, shaders et HLSL
  • Application de textures
  • LumiÃ¨re diffuse
  • Rendu de modÃ¨les 3D
  • Chargement de modÃ¨les 3D Maya

Cette page est valide HTML 5 et CSS 3

Copyright Â© 2014 - 2015 Developpez LLC. Aucune reproduction, mÃªme partielle, ne peut Ãªtre faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'Ã trois ans de prison et jusqu'Ã 300 000 â‚¬ de dommages et intÃ©rÃªts.

Responsable bÃ©nÃ©vole de la rubrique 2D - 3D - Jeux : LittleWhite -
Developpez.com

Nous contacter

Participez

Informations lÃ©gales

Services

Forum 2D - 3D - Jeux

Blogs

HÃ©bergement

Partenaires

PlanetHoster

Copyright Â© 2000-2015 - www.developpez.com


ASM Mode Reel

DirectX 11 - Tutoriel 1 : Mise en place de DirectX 11 avec Visual Studio

Table des matières

L'article Publié le 31 mars 2014
  • Navigation
  • II. Introduction
  • III. Configuration sous Visual Studio 2010
  • IV. Configuration sous Visual Studio 2008
  • V. Exercice à faire
  • VI. Remerciements
  • Navigation

Ce premier tutoriel vous aidera à préparer vos outils pour programmer avec DirectX 11 sous Visual Studio 2010. Vu dans .... http://jeux.developpez.com/tutoriels/directx-11-rastertek

Les deux auteur et traducteur http://www.rastertek.com RasterTek
Auteur RasterTek Traducteur : Jérôme Marsaguet
Version PDF Version hors-ligne

II. Introduction

Avant d'écrire n'importe quel code graphique, nous avons besoin des outils pour le faire. Le premier de ces outils est un compilateur qui est de préférence fourni avec un joli EDI. Celui que j'utiliserai et pour lequel je fournirai des dossiers de projet est Visual Studio 2010. Il y en a plusieurs de disponibles et certains sont même gratuits sur le Net. Je vous laisse le soin de décider lequel vous préférez.

Le deuxième outil dont vous aurez besoin est le SDK de DirectX. Il peut être téléchargé gratuitement depuis le site de Microsoft. Après téléchargement et installation du SDK, vous aurez les fichiers nécessaires pour compiler des programmes DirectX. Vous remarquerez également qu'il est livré avec des exemples, des outils et de la documentation. La documentation est plutôt bonne et comporte quelques exemples très simples dans la section des tutoriels DirectX 10 qui s'appliquent également à DirectX 11. Les outils fournis avec sont également utiles, nous utiliserons assez souvent celui des textures DDS. Le navigateur d'exemples donne également quelques démonstrations simples pour réaliser certains effets que vous cherchez peut-être à faire. Les exemples posséderont aussi généralement quelques-unes des nouvelles fonctionnalités de la dernière démonstration du SDK.

Les deux installés, vous pouvez maintenant configurer votre EDI pour travailler avec le SDK de DirectX 11. À noter que certains environnements de développement devront être installés avant le SDK.

III. Configuration sous Visual Studio 2010

Voici les étapes que j'ai suivies sous Visual Studio 2010 :

Premièrement, vous avez besoin de créer un projet Win32, donc sélectionnez Fichier → Nouveau → Projet. Puis sélectionnez « Projet Win32 » parmi les possibilités. Donnez un nom au projet (j'ai nommé le mien « Engine ») ainsi qu'un emplacement, cliquez sur « OK » puis cliquez sur « Suivant » pour obtenir un nouveau menu. Sous « Options supplémentaires » cochez la case « Projet vide » puis sur « Terminer ». Vous devriez avoir un projet Win32 de base vide.

Cliquez maintenant sur « Affichage » dans le menu et sélectionnez « Gestionnaire de propriétés ».

Cliquez sur la flèche à gauche du nom de votre projet (le mien était « Engine ») dans la fenêtre du gestionnaire de propriétés sur la partie gauche de l'écran. Cela affiche vos configurations de solution.

Sélectionnez le dossier « Debug | Win32 » et double-cliquez sur « Microsoft.Cpp.Win32.user ».

Dans la fenêtre qui s'affiche, sélectionnez « Répertoires VC++ » dans le menu de gauche. Ce qui affichera les dossiers que le projet utilise.

Maintenant, sélectionnez « Répertoires Include » de la liste des répertoires et cliquez sur la flèche vers le bas qui apparaît à gauche(1). Parmi les choix qui apparaissent, choisissez « Modifier ».

Cliquez sur l'icône du dossier en haut (appelée « Nouvelle Ligne » si vous passez la souris dessus) et cliquez sur le « … » qui apparaît à droite.

Allez dans le dossier include du SDK de DirectX, les différentes versions n'ont pas le même nom mais sur mon système, c'était : C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include.

Cliquez ensuite sur « Sélectionner un dossier » puis sur « OK ».

Le répertoire des bibliothèques est également nécessaire. Maintenant sélectionnez « Répertoires de bibliothèque » dans la liste de répertoires et cliquez sur la flèche qui apparaît à gauche. Parmi les choix qui apparaissent, choisissez « Modifier ».

Cliquez ensuite sur « Sélectionner un dossier » puis sur « OK ».

Le répertoire des bibliothèques doit aussi être changé, donc sélectionnez maintenant « Répertoire de bibliothèques » de la liste des répertoires et cliquez sur la flèche vers le bas qui apparaît à gauche(2). Parmi les choix qui apparaissent, choisissez « Modifier ».

Cliquez sur l'icône du dossier en haut (appelée « Nouvelle Ligne » si vous passez la souris dessus) et cliquez sur le « … » qui apparaît à droite.

Allez dans le dossier des bibliothèques du SDK de DirectX, les différentes versions n'ont pas le même nom mais sur mon système, c'était : C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86.

Cliquez ensuite sur « Sélectionner un dossier » puis sur « OK ».

Cliquez finalement sur le bouton « Appliquer » en bas à droite de la fenêtre « Pages de propriétés ».

Cliquez maintenant sur « OK » pour fermer la fenêtre et valider vos paramètres. Dans le menu, cliquez sur « Affichage » puis « Explorateur de solutions » pour revenir à votre code. Tous les projets DirectX devraient maintenant compiler correctement et l'erreur « Impossible d'ouvrir le fichier include: 'd3d11.h': No such file or directory » devrait disparaître. À noter également que ces paramètres seront automatiquement appliqués à la configuration « Release | Win32 », vous n'avez donc ici pas besoin de les paramétrer. En outre, ils devraient persister pour tous les nouveaux projets et n'auront plus jamais besoin d'être à nouveau réglés.

IV. Configuration sous Visual Studio 2008

Voici les étapes que j'ai suivies sous Visual Studio 2008 :

Cliquez sur « Outils » dans le menu.

Sélectionnez « Options » à la fin de la liste.

Cliquez sur « Projets et solutions » dans la liste de gauche.

Cliquez sur « Répertoires de VC++ » dans la liste déployée.

Sur le côté droit sous « Afficher les répertoires pour : », sélectionnez « Fichiers Includes » dans la liste déroulante.

Ajoutez maintenant l'emplacement du dossier « include » en haut de la liste. C'était le suivant sur mon système, mais il peut être différent sur le vôtre (mois de la version, etc.), assurez-vous de bien vérifier : C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include.

Après avoir effectué cet ajout, vous devez ajouter le dossier de la bibliothèque aussi. Sur le côté droit sous « Afficher les répertoires pour : », sélectionnez « Fichiers bibliothèques » dans la liste déroulante.

C'était le suivant sur mon système, mais il peut être différent sur le vôtre (mois de la version, etc.), assurez-vous de bien vérifier :

C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86

Avec ces deux dossiers paramétrés dans Visual Studio, vous devriez maintenant être capable de compiler du code DirextX 11 sans problème.

V. Exercice à faire

  1. Compilez et exécutez le Tutoriel #2 de la documentation du SDK de DirectX 11 (la source est située dans : (SDK root)\Samples\C++\Direct3D10\Tutorials\Tutorial02)

VI. Remerciements

Cet article est une traduction autorisée de l'article paru sur ... http://www.rastertek.com/dx11tut01.html ... RasterTek.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Craig. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

LittleWhite -- http://www.developpez.net --


ASM Mode Reel

DirectX 11 - Tutoriel 2 : Création du cadre de travail et d'une fenêtre

Table des matières

  • Navigation
  • II. Introduction
  • III. Le cadre de travail
  • IV. WinMain
  • V. Systemclass.h
  • VI. Systemclass.cpp
  • VII. Inputclass.h
  • VIII. Inputclass.cpp
  • IX. Graphicsclass.h
  • X. Graphicsclass.cpp
  • XI. Résumé
  • XII. Exercices à faire
  • XIII. Code source
  • XIV. Remerciements
  • Navigation

Avant de commencer à coder avec DirectX 11, je vous recommande de construire un squelette de code simple. Cette ossature prendra en charge les fonctionnalités de base des fenêtres et vous fournira un moyen simple d'étendre le code de façon organisée et lisible.

II. Introduction

Avant de commencer à coder avec DirectX 11, je vous recommande de construire un squelette de code simple. Cette ossature prendra en charge les fonctionnalités de base des fenêtres et vous fournira un moyen simple d'étendre le code de façon organisée et lisible. Comme le but de ces tutoriels est juste d'essayer différentes fonctionnalités de DirectX 11, nous garderons une base aussi claire que possible.

III. Le cadre de travail

Le cadre de travail débutera par quatre éléments. Une fonction WinMain pour traiter le point d'entrée de l'application. Une classe System encapsulant l'ensemble de l'application qui sera appelée au sein de la fonction WinMain. Dans la classe System, nous aurons une classe Input pour la gestion des entrées de l'utilisateur et une classe Graphics pour manipuler le code graphique DirectX. Voici un diagramme de cette configuration :

Image non disponible

Maintenant que nous voyons comment le squelette est organisé, commençons par regarder la fonction WinMain dans le fichier main.cpp.

IV. WinMain

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : main.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow)
{
    SystemClass* System;
    bool result;

    // Créer l'objet System.
    System = new SystemClass;
    if(!System)
    {
        return 0;
    }

    // Initialiser et exécuter l'objet System.
    result = System->Initialize();
    if(result)
    {
        System->Run();
    }

    // Fermer et libérer l'objet System.
    System->Shutdown();
    delete System;
    System = 0;

    return 0;
}

Comme vous pouvez le voir nous avons gardé la fonction WinMain assez simple. Nous créons un objet System, puis l'initialisons. S'il s'initialise sans problème, nous appelons sa méthode Run qui exécutera sa propre boucle ainsi que tout le code de l'application jusqu'à ce qu'elle se termine. Après cela, nous fermons l'objet System et le libérons. Nous avons donc fait très simple et encapsulé toute l'application à l'intérieur de la classe System. Jetons maintenant un œil au fichier d'en-tête de la classe System.

V. Systemclass.h

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_

Nous définissons ici WIN32_LEAN_AND_MEAN. Nous faisons cela pour accélérer la compilation, réduisant la taille des fichiers d'en-tête Win32 en excluant certaines bibliothèques moins utilisées.

Sélectionnez
///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN

Windows.h est inclus afin que nous puissions appeler les fonctions pour créer/détruire des fenêtres et utiliser les autres fonctions Win32 utiles.

Sélectionnez
//////////////
// INCLUDES //
//////////////
#include <windows.h>

Nous avons inclus les en-têtes des deux autres classes de notre ensemble de composants ici afin que nous puissions les utiliser dans la classe System.

Sélectionnez
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"

La définition de la classe est assez simple. Nous voyons la définition des méthodes Initialize, Shutdown, et Run qui sont appelées dans WinMain, ainsi que quelques fonctions privées qui seront appelées à l'intérieur de ces fonctions. Nous avons également une fonction MessageHandler dans la classe pour gérer les messages du système de fenêtres envoyés à l'application durant son exécution. Enfin, nous avons quelques variables privées m_Input et m_Graphics qui pointeront vers les deux objets se chargeant des graphiques et des entrées.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
    SystemClass();
    SystemClass(const SystemClass&);
    ~SystemClass();

    bool Initialize();
    void Shutdown();
    void Run();

    LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

private:
    bool Frame();
    void InitializeWindows(int&, int&);
    void ShutdownWindows();

private:
    LPCWSTR m_applicationName;
    HINSTANCE m_hinstance;
    HWND m_hwnd;

    InputClass* m_Input;
    GraphicsClass* m_Graphics;
};

/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;

#endif

La fonction WndProc et le pointeur ApplicationHandle sont également inclus dans ce fichier de classe afin que nous puissions rediriger les messages du système de fenêtres dans notre méthode MessageHandler de la classe System.

Jetons maintenant un œil au fichier source de la classe System :

VI. Systemclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"

Dans le constructeur de la classe, j'initialise les pointeurs d'objet à NULL. Ceci est important parce que si l'initialisation de ces objets échoue, la fonction Shutdown tentera plus tard de libérer ces objets. S'ils ne sont pas NULL, alors cela suppose qu'ils pointent vers des objets valides créés et ayant besoin d'être libérés. Il est également recommandé d'initialiser tous les pointeurs et les variables à NULL dans vos applications. Certaines compilations en release échoueront si vous ne le faites pas.

Sélectionnez
SystemClass::SystemClass()
{
    m_Input = 0;
    m_Graphics = 0;
}

Je crée ici un destructeur et un constructeur de copie vides. Je n'ai pas besoin d'eux dans cette classe, mais s'ils ne sont pas définis, certains compilateurs vont les générer pour vous, et je préfère ici qu'ils soient vides.

Vous remarquerez aussi que je ne libère aucun objet dans le destructeur de la classe. Je fais tout cela dans la méthode Shutdown que vous verrez plus tard. La raison est que je n'ai pas la certitude qu'elle est appelée. Certaines fonctions de Windows comme ExitThread() sont connues pour ne pas appeler vos destructeurs de classe, produisant des fuites de mémoire. Vous pouvez dorénavant bien sûr appeler des versions plus sûres de ces fonctions, mais je préfère être très prudent lorsque je programme sous Windows.

Sélectionnez
SystemClass::SystemClass(const SystemClass& other)
{
}

SystemClass::~SystemClass()
{
}

La fonction Initialize qui suit effectue l'installation de l'application. Elle appelle d'abord InitializeWindows qui crée la fenêtre, puis crée et initialise les objets Input et Graphics que l'application utilisera pour la gestion des entrées de l'utilisateur et du rendu.

Sélectionnez
bool SystemClass::Initialize()
{
    int screenWidth, screenHeight;
    bool result;

    // Initialise la largeur et hauteur de l'écran à zéro avant d'envoyer les variables à la fonction.
    screenWidth = 0;
    screenHeight = 0;

    // Initialise la fenêtre de l'interface
    InitializeWindows(screenWidth, screenHeight);

    // Crée l'objet Input. Cet objet sera utilisé pour gérer la saisie au clavier par l'utilisateur.
    m_Input = new InputClass;
    if(!m_Input)
    {
        return false;
    }

    // Initialise l'objet Input.
    m_Input->Initialize();

    // Crée l'objet Graphics. Cet objet gérera tous les graphiques de l'application.
    m_Graphics = new GraphicsClass;
    if(!m_Graphics)
    {
        return false;
    }

    // Initialise l'objet Graphics.
    result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
    if(!result)
    {
        return false;
    }

    return true;
}

La fonction Shutdown fait le nettoyage. Elle arrête et libère tout ce qui est associé aux objets Graphics et Input. Elle ferme également la fenêtre et libère les identifiants qui lui sont associés.

Sélectionnez
void SystemClass::Shutdown()
{
    // Libère l'objet Graphics.
    if(m_Graphics)
    {
        m_Graphics->Shutdown();
        delete m_Graphics;
        m_Graphics = 0;
    }

    // Libère l'objet Input.
    if(m_Input)
    {
        delete m_Input;
        m_Input = 0;
    }

    // Ferme la fenêtre.
    ShutdownWindows();

    return;
}

La méthode Run est celle qui met en boucle notre application et effectue tous les traitements jusqu'à ce que nous décidions de quitter l'application. Les traitements sont effectués dans la fonction Frame appelée à chaque itération. Il s'agit d'un concept important à comprendre car le reste de notre application doit être écrit dans cet esprit. Le pseudo-code ressemble à ce qui suit :

Sélectionnez

tant que la variable 'done' est à false
    vérifier les messages système de la fenêtre
    traiter les messages système
    traiter la boucle de l'application
    vérifier si l'utilisateur a voulu fermer l'application durant le traitement
Sélectionnez
void SystemClass::Run()
{
    MSG msg;
    bool done, result;

    // Initialise la structure de message.
    ZeroMemory(&msg, sizeof(MSG));

    // Boucler jusqu'à obtenir un message de sortie de la fenêtre ou de l'utilisateur.
    done = false;
    while(!done)
    {
        // Gérer les messages de la fenêtre.
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // Si la fenêtre demande à fermer l'application, alors on sort.
        if(msg.message == WM_QUIT)
        {
            done = true;
        }
        else
        {
            // Autrement, effectuer le traitement de la trame.
            result = Frame();
            if(!result)
            {
                done = true;
            }
        }

    }

    return;

}

La fonction Frame qui suit est celle qui effectue les traitements de notre application. Jusque-là c'est assez simple, nous interrogeons l'objet Input pour contrôler si l'utilisateur a appuyé sur la touche Échap, voulant fermer l'application. S'il ne veut pas, alors nous utilisons l'objet Graphics pour effectuer les traitements de rendu de cette trame. Au fur et à mesure que notre application évoluera, nous y rajouterons du code.

Sélectionnez
bool SystemClass::Frame()
{
    bool result;

    // Vérifie si l'utilisateur a appuyé sur Échap pour fermer l'application.
    if(m_Input->IsKeyDown(VK_ESCAPE))
    {
        return false;
    }

    // Effectue le traitement de la trame pour les objets Graphics.
    result = m_Graphics->Frame();
    if(!result)
    {
        return false;
    }

    return true;
}

C'est dans la méthode MessageHandler où nous traitons les messages système de la fenêtre. De cette façon, nous pouvons écouter certains évènements qui nous intéressent. Actuellement nous allons juste vérifier si une touche est enfoncée ou relâchée et transmettre cette information à l'objet Input. Tous les autres évènements seront passés au gestionnaire de messages par défaut de Windows.

Sélectionnez
LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    switch(umsg)
    {
        // Vérifie si une touche a été pressée.
        case WM_KEYDOWN:
        {
            // Si oui, on l'envoie à l'objet Input afin qu'il sauvegarde cet état.
            m_Input->KeyDown((unsigned int)wparam);
            return 0;
        }

        // Vérifie si une touche a été relâchée.
        case WM_KEYUP:
        {
            // Si oui, on l'envoie à l'objet Input afin qu'il supprime l'état de cette touche.
            m_Input->KeyUp((unsigned int)wparam);
            return 0;
        }

        // Les autres messages sont envoyés au gestionnaire de message par défaut. Nous n'en avons pas besoin dans notre application.
        default:
        {
            return DefWindowProc(hwnd, umsg, wparam, lparam);
        }
    }
}

Dans la méthode InitializeWindows, nous mettons le code pour créer la fenêtre utilisée pour l'affichage. Elle retourne les valeurs screenWidth et screenHeight à la fonction qui l'appelle afin que nous puissions les utiliser dans l'application. À l'aide de paramètres par défaut, nous créons une fenêtre noire sans bordures et l'initialisons. La fonction créera une fenêtreen plein écran ou non selon la variable globale FULL_SCREEN. Si elle est fixée à true alors l'écran couvre toute la fenêtre du bureau de l'utilisateur ; si elle est définie à false nous avons juste une fenêtre 800x600 au milieu de l'écran. J'ai placé la variable globale FULL_SCREEN au début du fichier graphicsclass.h au cas où vous souhaiteriez la modifier. Ce pourquoi je l'ai placée dans ce fichier plutôt que dans l'en-tête de la classe prendra tout son sens plus tard.

Sélectionnez
void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight)
{
    WNDCLASSEX wc;
    DEVMODE dmScreenSettings;
    int posX, posY;

    // Obtention d'un pointeur externe de cet objet.
    ApplicationHandle = this;

    // Obtention de l'instance de cette application.
    m_hinstance = GetModuleHandle(NULL);

    // Nom de l'application.
    m_applicationName = L"Engine";

    // Configure la classe de la fenêtre avec les réglages par défaut.
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = m_hinstance;
    wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    wc.hIconSm = wc.hIcon;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = m_applicationName;
    wc.cbSize = sizeof(WNDCLASSEX);

    // Enregistre la classe de fenêtre.
    RegisterClassEx(&wc);

    // Détermine la résolution de l'écran.
    screenWidth  = GetSystemMetrics(SM_CXSCREEN);
    screenHeight = GetSystemMetrics(SM_CYSCREEN);

    // Configuration, des paramètres de l'écran suivant s'il est exécuté en mode plein écran ou en mode fenêtré.
    if(FULL_SCREEN)
    {
        // En plein écran, on règle l'écran à la taille maximale du bureau en 32 bits.
        memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
        dmScreenSettings.dmSize       = sizeof(dmScreenSettings);
        dmScreenSettings.dmPelsWidth  = (unsigned long)screenWidth;
        dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight;
        dmScreenSettings.dmBitsPerPel = 32;
        dmScreenSettings.dmFields     = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

        // Modification des paramètres d'affichage en plein écran.
        ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);

        // Positionnement de la fenêtre dans le coin supérieur gauche..
        posX = posY = 0;
    }
    else
    {
        // En fenêtré, fixer une résolution de 800x600.
        screenWidth  = 800;
        screenHeight = 600;

        // Centre la fenêtre sur l'écran.
        posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth)  / 2;
        posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2;
    }

    // Crée la fenêtre selon les paramètres précédents et récupère son identifiant.
    m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName,
                WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP,
                posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL);

    // Affiche la fenêtre à l'écran et lui donne la main.
    ShowWindow(m_hwnd, SW_SHOW);
    SetForegroundWindow(m_hwnd);
    SetFocus(m_hwnd);

    // Cache le pointeur de la souris.
    ShowCursor(false);

    return;
}

ShutdownWindows fait juste ce quelle signifie. Elle remet les paramètres de l'écran à la normale et libère la fenêtre et les identifiants qui lui sont associés.

Sélectionnez
void SystemClass::ShutdownWindows()
{
    // Montre le curseur de la souris.
    ShowCursor(true);

    // Change les paramètres d'affichage si l'on quitte le mode plein écran.
    if(FULL_SCREEN)
    {
        ChangeDisplaySettings(NULL, 0);
    }

    // Détruit la fenêtre.
    DestroyWindow(m_hwnd);
    m_hwnd = NULL;

    // Détruit l'instance de l'application.
    UnregisterClass(m_applicationName, m_hinstance);
    m_hinstance = NULL;

    // Efface toute trace de la classe.
    ApplicationHandle = NULL;

    return;
}

La méthode WndProc est celle dans laquelle la fenêtre envoie ses messages. Vous remarquerez que nous donnons le nom de la fenêtre lorsque l'on initialise la classe de la fenêtre avec wc.lpfnWndProc = WndProc dans la méthode InitializeWindows ci-dessus. Je l'ai incluse dans ce fichier de classe puisque nous l'utilisons directement dans la classe System en lui faisant envoyer tous les messages à la méthode MessageHandler définie dans SystemClass. Cela nous permet d'attacher les fonctionnalités de messagerie directement dans notre classe et conserver un code propre.

Sélectionnez
LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam)
{
    switch(umessage)
    {
        // Vérifie si la fenêtre va être détruite.
        case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }

        // Vérifie si la fenêtre va être fermée.
        case WM_CLOSE:
        {
            PostQuitMessage(0);
            return 0;
        }

        // Tous les autres messages sont passés au gestionnaire de messages de la classe système.
        default:
        {
            return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam);
        }
    }
}

VII. Inputclass.h

Pour garder les tutoriels simples, j'ai réservé les entrées fenêtre jusqu'au moment où je ferai un tutoriel sur DirectInput (qui est de loin supérieur). La classe Input gère la saisie au clavier de l'utilisateur. Cette classe est donnée en entrée de la fonction SystemClass::MessageHandler. L'objet Input va stocker l'état de chaque touche dans un tableau de touches. Lorsqu'interrogé, il renseignera les fonctions appelantes si une certaine touche est enfoncée. Voici l'en-tête :

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : inputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : InputClass
////////////////////////////////////////////////////////////////////////////////
class InputClass
{
public:
    InputClass();
    InputClass(const InputClass&);
    ~InputClass();

    void Initialize();

    void KeyDown(unsigned int);
    void KeyUp(unsigned int);

    bool IsKeyDown(unsigned int);

private:
    bool m_keys[256];
};

#endif

VIII. Inputclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : inputclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "inputclass.h"

InputClass::InputClass()
{
}

InputClass::InputClass(const InputClass& other)
{
}

InputClass::~InputClass()
{
}

void InputClass::Initialize()
{
    int i;

    // Initialise toutes les touches comme relâchées, et non pressées.
    for(i=0; i<256; i++)
    {
        m_keys[i] = false;
    }

    return;
}

void InputClass::KeyDown(unsigned int input)
{
    // Si une touche est pressée, on sauvegarde son état dans le tableau.
    m_keys[input] = true;
    return;
}

void InputClass::KeyUp(unsigned int input)
{
    // Si une touche est relâchée, on supprime cet état du tableau.
    m_keys[input] = false;
    return;
}

bool InputClass::IsKeyDown(unsigned int key)
{
    // Retourne l'état de la touche (pressée/relâchée).
    return m_keys[key];
}

IX. Graphicsclass.h

La classe System crée également un objet Graphics. Toutes les fonctionnalités graphiques de cette application seront encapsulées dans cette classe. J'utiliserai aussi l'en-tête de ce fichier pour tous les paramètres graphiques globaux qui nous souhaiterons changer comme le mode plein écran ou fenêtré. Actuellement, cette classe est vide, mais dans les tutoriels à venir, elle contiendra tous les objets graphiques.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

//////////////
// INCLUDES //
//////////////
#include <windows.h>

/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

Pour commencer, nous aurons besoin de ces quatre variables globales.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();

    bool Initialize(int, int, HWND);
    void Shutdown();
    bool Frame();

private:
    bool Render();

private:

};

#endif

X. Graphicsclass.cpp

J'ai gardé cette classe entièrement vide pour l'instant, étant donné que nous sommes en train de construire le squelette dans ce tutoriel.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

GraphicsClass::GraphicsClass()
{
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{

    return true;
}

void GraphicsClass::Shutdown()
{

    return;
}

bool GraphicsClass::Frame()
{

    return true;
}

bool GraphicsClass::Render()
{

    return true;
}

XI. Résumé

Nous avons donc maintenant une base de travail et une fenêtre qui apparaît à l'écran. Ce cadre va servir de base pour tous les tutoriels à venir. Sa compréhension est donc assez importante. S'il vous plaît essayez de faire l'exercice afin de vous assurer que le code compile et fonctionne avant de passer au prochain tutoriel. Si vous ne comprenez pas cette base de travail, avancez sur les autres tutoriels, ils pourront vous aider à mieux comprendre ce squelette lors de son utilisation.

XII. Exercices à faire

Changez le paramètre FULL_SCREEN à true dans l'en-tête graphicsclass.h puis recompilez et exécutez le programme. Appuyez sur la touche Échap pour quitter l'application une fois la fenêtre affichée.

XIII. Code source

Projet Visual Studio 2010 : dx11tut02.zip

Source seule : dx11src02.zip

Executable seul : dx11exe02.zip

XIV. Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek.

Copyright © 2000-2015 - www.developpez.com


ASM Mode Reel

DirectX 11 - Tutoriel 3 : initialisation de DirectX 11

Table des matières

  • Navigation
  • II. Liste des composants mis à jour
  • III. Graphicsclass.h
  • IV. Graphicsclass.cpp
  • V. D3dclass.h
  • VI. D3dclass.cpp
  • VII. Résumé
    • VII-A. Exercices à faire
  • VIII. Code source
  • IX. Remerciements
  • Navigation

Dans ce tutoriel, nous allons commencer à travailler avec DirectX 11. Nous initialiserons et fermerons Direct3D et effectuerons le rendu dans une fenêtre.

II. Liste des composants mis à jour

Nous allons ajouter à notre base une autre classe, que nous appellerons D3DClass, qui gérera toutes les fonctions du système Direct3D. Voici le nouveau diagramme :

Comme vous pouvez le voir, la classe D3DClass sera contenue dans la classe GraphicsClass. Le tutoriel précédent mentionnait que toutes les classes touchant les graphiques seraient situées dans la classe GraphicsClass, c'est donc la meilleure place pour notre nouvelle classe D3DClass. Intéressons-nous maintenant aux changements effectués dans la classe GraphicsClass.

III. Graphicsclass.h

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

Voici le premier changement. Nous avons enlevé le fichier d'en-tête windows.h et inclus d3dclass.h à la place.

Sélectionnez
///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"

/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();

    bool Initialize(int, int, HWND);
    void Shutdown();
    bool Frame();

private:
    bool Render();

private:

Et le deuxième changement se situe au niveau du pointeur vers la classe D3DClass que nous avons nommé m_D3D. Si vous vous le demandez, j'utilise le préfixe m_ pour tous les attributs de classe. Je peux ainsi aisément me rappeler les variables qui sont membres de ma classe et celles qui ne le sont pas.

Sélectionnez
    D3DClass* m_D3D;
};

#endif

IV. Graphicsclass.cpp

Si vous vous rappelez le tutoriel précédent, cette classe ne contenait aucun code. Maintenant que nous avons la classe membre D3DClass, nous allons commencer à la remplir par l'initialisation et la libération de notre objet D3DClass. Nous allons aussi rajouter des appels à BeginScene et EndScene dans la fonction Render afin de dessiner notre fenêtre en utilisant Direct3D.

Le tout premier changement se situe dans le constructeur. Nous initialisons le pointeur à zéro pour des raisons de sécurité, comme nous le faisons pour tous les pointeurs de classe.

Sélectionnez
GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
}

Le deuxième changement est dans la fonction Initialize. Nous créons ici l'objet D3DClass et appelons sa fonction Initialize. Nous envoyons à cette fonction les largeur et hauteur de l'écran, l'identifiant de la fenêtre, et les quatre variables globales du fichier Graphicsclass.h. La classe D3DClass va utiliser toutes ces variables pour mettre en place le système Direct3D. Nous y reviendrons plus en détails lorsque nous étudierons le fichier d3dclass.cpp.

Sélectionnez
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    bool result;

    // Crée l'objet Direct3D.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Initialise l'objet Direct3D.
    result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
        return false;
    }

    return true;
}

La nouvelle modification se trouve dans la fonction ShutDown de la classe GraphicsClass. Tous les objets graphiques sont détruits ici, l'appel de la méthode Shutdown de la classe D3DClass s'effectue donc ici. Remarquez que je vérifie avant si le pointeur est initialisé ou non. S'il ne l'est pas, nous pouvons supposer qu'il n'a jamais été construit, et n'a donc pas besoin d'être détruit. C'est pourquoi il est très important d'assigner à tous les pointeurs la valeur NULL dans le constructeur. S'il décèle que le pointeur a été initialisé, il tentera de finaliser l'objet D3DClass puis de libérer le pointeur.

Sélectionnez
void GraphicsClass::Shutdown()
{
    if(m_D3D)
    {
        m_D3D->Shutdown();
        delete m_D3D;
        m_D3D = 0;
    }

    return;
}

La méthode Frame a été mise à jour pour maintenant appeler la fonction Render à chaque trame.

Sélectionnez
bool GraphicsClass::Frame()
{
    bool result;

    // Effectue le rendu de la scène.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}

Le dernier changement dans cette fonction a lieu dans la fonction Render. Nous utilisons l'objet D3DClass pour remplir l'écran de gris. Puis appelons EndScene afin que la couleur apparaisse dans la fenêtre.

Sélectionnez
bool GraphicsClass::Render()
{
    // Efface les tampons de la scène.
    m_D3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);

    // Affiche la scène rendue à l'écran.
    m_D3D->EndScene();

    return true;
}

Jetons maintenant un coup d'œil au nouveau fichier d'en-tête de la classe D3DClass :

V. D3dclass.h

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_

Première chose, nous précisons dans l'en-tête les bibliothèques à lier lors de l'utilisation de ce module. Ces bibliothèques contiennent toutes les fonctionnalités de Direct3D pour la mise en place et le dessin des graphiques 3D dans DirectX ainsi que des outils d'interface avec le matériel de l'ordinateur permettant d'obtenir des informations sur la fréquence de rafraîchissement de l'écran, la carte vidéo utilisée, et ainsi de suite. Vous remarquerez que certaines bibliothèques DirectX 10 sont encore utilisées, c'est parce qu'elles n'ont jamais été mises à jour pour DirectX 11, leur fonctionnalité n'ayant pas besoin de changer.

Sélectionnez
/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")

Ensuite, nous incluons les en-têtes de ces mêmes bibliothèques que nous venons de lier, ainsi que ceux contenant les définitions de types DirectX et autres.

Sélectionnez
//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>

La définition de la classe D3DClass est aussi simple que possible ici. Elle possède le constructeur par défaut, le constructeur de copie et le destructeur. Plus important, les méthodes Initialize et Shutdown. C'est ce sur quoi nous allons principalement nous concentrer dans ce tutoriel. En dehors de cela, des fonctions auxiliaires sans importance ici et un certain nombre d'attributs privés que nous examinerons en même temps que le fichier d3dclass.cpp. Pour l'instant, concentrez-vous juste sur les méthodes Initialize et Shutdown.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
    D3DClass();
    D3DClass(const D3DClass&);
    ~D3DClass();

    bool Initialize(int, int, bool, HWND, bool, float, float);
    void Shutdown();

    void BeginScene(float, float, float, float);
    void EndScene();

    ID3D11Device* GetDevice();
    ID3D11DeviceContext* GetDeviceContext();

    void GetProjectionMatrix(D3DXMATRIX&);
    void GetWorldMatrix(D3DXMATRIX&);
    void GetOrthoMatrix(D3DXMATRIX&);

    void GetVideoCardInfo(char*, int&);

private:
    bool m_vsync_enabled;
    int m_videoCardMemory;
    char m_videoCardDescription[128];
    IDXGISwapChain* m_swapChain;
    ID3D11Device* m_device;
    ID3D11DeviceContext* m_deviceContext;
    ID3D11RenderTargetView* m_renderTargetView;
    ID3D11Texture2D* m_depthStencilBuffer;
    ID3D11DepthStencilState* m_depthStencilState;
    ID3D11DepthStencilView* m_depthStencilView;
    ID3D11RasterizerState* m_rasterState;
    D3DXMATRIX m_projectionMatrix;
    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_orthoMatrix;
};

#endif

Pour ceux qui connaissent déjà Direct3D, vous remarquerez peut-être que cette classe ne contient pas de matrice de vue. Celle-ci sera utilisée dans une classe Camera que verrons dans un prochain tutoriel.

VI. D3dclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"

Comme la plupart des classes, nous commençons par initialiser tous les pointeurs membres à la valeur nulle dans le constructeur. Tous les pointeurs du fichier d'en-tête ont été pris en compte ici.

Sélectionnez
D3DClass::D3DClass()
{
    m_swapChain = 0;
    m_device = 0;
    m_deviceContext = 0;
    m_renderTargetView = 0;
    m_depthStencilBuffer = 0;
    m_depthStencilState = 0;
    m_depthStencilView = 0;
    m_rasterState = 0;
}

D3DClass::D3DClass(const D3DClass& other)
{
}

D3DClass::~D3DClass()
{
}

La fonction d'initialisation se charge de toute la configuration de Direct3D pour DirectX 11. J'ai mis tout le code nécessaire ici, ainsi que quelques éléments supplémentaires qui nous aideront dans les tutoriels à venir. J'aurais pu effectuer des simplifications et supprimer certains points, mais il est préférable de couvrir tout cela dans un unique tutoriel dédié.

Les variables ScreenWidth et screenHeight passées à cette fonction sont la largeur et la hauteur de la fenêtre que nous avons créée dans la classe SystemClass. Direct3D va les utiliser pour s'initialiser et utiliser ces mêmes dimensions pour la fenêtre. La variable hwnd est l'identifiant de la fenêtre ; Direct3D en aura besoin pour accéder à la fenêtre précédemment créée. La variable fullscreen sert à savoir si nous sommes en mode fenêtré ou plein écran. Direct3D a aussi besoin de cette information afin de créer la fenêtre avec les bons paramètres. Les variables screendepth et screenNear servent au réglage de la profondeur de notre environnement 3D qui sera rendu dans la fenêtre. La variable vsync indique si nous voulons que Direct3D effectue le rendu à la fréquence de rafraîchissement de l'écran de l'utilisateur ou le plus vite possible.

Sélectionnez
bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen,
              float screenDepth, float screenNear)
{
    HRESULT result;
    IDXGIFactory* factory;
    IDXGIAdapter* adapter;
    IDXGIOutput* adapterOutput;
    unsigned int numModes, i, numerator, denominator, stringLength;
    DXGI_MODE_DESC* displayModeList;
    DXGI_ADAPTER_DESC adapterDesc;
    int error;
    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    D3D_FEATURE_LEVEL featureLevel;
    ID3D11Texture2D* backBufferPtr;
    D3D11_TEXTURE2D_DESC depthBufferDesc;
    D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
    D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
    D3D11_RASTERIZER_DESC rasterDesc;
    D3D11_VIEWPORT viewport;
    float fieldOfView, screenAspect;

    // Stocke le réglage de la synchronisation verticale
    m_vsync_enabled = vsync;

Avant que nous puissions initialiser Direct3D, nous devons obtenir le taux de rafraîchissement de la carte graphique/du moniteur, informations légèrement différentes selon chaque ordinateur ; cette information est donc nécessaire. Nous acquérons les valeurs du numérateur et du dénominateur que nous passons ensuite lors de l'initialisation à DirectX, qui va calculer le taux de rafraîchissement approprié. Si nous ne le faisons pas et demandons une valeur de fréquence de rafraîchissement par défaut, celle-ci n'existant pas sur tous les ordinateurs, DirectX effectuera un blit à la place d'un échange de tampons qui dégradera les performances et nous donnera des erreurs gênantes à la sortie de débogage.

Sélectionnez
    // Crée une interface de fabrique graphique DirectX.
    result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
    if(FAILED(result))
    {
        return false;
    }

    // Utilise la fabrique pour créer un adaptateur pour l'interface graphique principale (carte graphique).
    result = factory->EnumAdapters(0, &adapter);
    if(FAILED(result))
    {
        return false;
    }

    // Énumère la sortie de l'adaptateur principal (écran).
    result = adapter->EnumOutputs(0, &adapterOutput);
    if(FAILED(result))
    {
        return false;
    }

    // Obtient le nombre de modes correspondant au format d'affichage DXGI_FORMAT_R8G8B8A8_UNORM pour l'adaptateur principal (écran).
    result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
    if(FAILED(result))
    {
        return false;
    }

    // Crée une liste pour stocker tous les modes d'affichage possibles pour cette combinaison écran/carte graphique.
    displayModeList = new DXGI_MODE_DESC[numModes];
    if(!displayModeList)
    {
        return false;
    }

    // Remplit les structures de la liste des modes.
    result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
    if(FAILED(result))
    {
        return false;
    }

    // Parcourt tous les modes d'affichage, et trouve celui qui correspond à la taille de l'écran.
    // Quand une correspondance est trouvée, stocke le numérateur et dénominateur du taux de rafraîchissement de l'écran.
    for(i=0; i<numModes; i++)
    {
        if(displayModeList[i].Width == (unsigned int)screenWidth)
        {
            if(displayModeList[i].Height == (unsigned int)screenHeight)
            {
                numerator = displayModeList[i].RefreshRate.Numerator;
                denominator = displayModeList[i].RefreshRate.Denominator;
            }
        }
    }

Nous possédons le numérateur et le dénominateur du taux de rafraîchissement. La dernière chose à récupérer en utilisant l'adaptateur est le nom de la carte vidéo et sa quantité de mémoire.

Sélectionnez
    // Obtient la description de l'adaptateur (carte graphique).
    result = adapter->GetDesc(&adapterDesc);
    if(FAILED(result))
    {
        return false;
    }

    // Stocke la valeur de la mémoire graphique dédiée en mégaoctets.
    m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

    // Convertit le nom de la carte graphique en tableau de caractères et le stocke.
    error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);
    if(error != 0)
    {
        return false;
    }

Maintenant que nous avons stocké le numérateur et le dénominateur du taux de rafraîchissement, et les informations sur la carte vidéo, nous pouvons libérer les structures et interfaces utilisées pour les obtenir.

Sélectionnez
    // Libère la liste des modes d'affichage.
    delete [] displayModeList;
    displayModeList = 0;

    // Libère la sortie de l'adaptateur.
    adapterOutput->Release();
    adapterOutput = 0;

    // Libère l'adaptateur.
    adapter->Release();
    adapter = 0;

    // Libère la fabrique.
    factory->Release();
    factory = 0;

Le taux de rafraîchissement du système obtenu, nous pouvons débuter l'initialisation de DirectX. La première chose que nous allons faire est de définir la chaîne de permutation. Elle se compose des tampons avant et arrière sur lesquels seront dessinés les graphismes. En général nous utilisons un unique tampon arrière, y dessinons tout, puis l'échangeons avec le tampon avant qui s'affiche alors sur l'écran de l'utilisateur. C'est pourquoi nous l'appelons une « chaîne de permutation ».

Sélectionnez
    // Initialise la chaîne de permutation.
    ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

    // Utilise un unique tampon arrière.
    swapChainDesc.BufferCount = 1;

    // Règle la largeur et la hauteur du tampon arrière.
    swapChainDesc.BufferDesc.Width = screenWidth;
    swapChainDesc.BufferDesc.Height = screenHeight;

    // Fixe un format 32 bits pour le tampon arrière.
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

La partie suivante de la définition de la chaîne de permutation est le taux de rafraîchissement, qui désigne combien de fois par seconde le tampon arrière est copié dans le tampon avant. Si vsync est mis à true dans notre en-tête graphicsclass.h, il suivra le taux d'actualisation des réglages système (par exemple 60 Hz). Cela signifie que l'écran sera redessiné 60 fois par seconde (ou plus si le taux de rafraîchissement système est supérieur à 60). Maintenant, si nous mettons vsync à false, il redessinera l'écran autant de fois par seconde qu'il le peut, ceci pouvant provoquer des artefacts visuels.

Sélectionnez
    // Règle le taux de rafraîchissement du tampon arrière.
    if(m_vsync_enabled)
    {
        swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
        swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
    }
    else
    {
        swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
        swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
    }

    // Définit le rôle du tampon arrière.
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

    // Définit l'identifiant de la fenêtre de rendu.
    swapChainDesc.OutputWindow = hwnd;

    // Désactive le multi-échantillonnage.
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;

    // Détermine le mode plein écran ou fenêtré.
    if(fullscreen)
    {
        swapChainDesc.Windowed = false;
    }
    else
    {
        swapChainDesc.Windowed = true;
    }

    // Définit l'ordre de balayage des lignes et le redimensionnement à non-spécifié.
    swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    // Supprime le contenu du tampon arrière après affichage.
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    // Aucun flag avancé.
    swapChainDesc.Flags = 0;

La description de la chaîne de permutation faite, nous avons aussi besoin de configurer une variable appelée « niveau de fonctionnalité ». Celle-ci indique quelle version de DirectX nous souhaitons utiliser. Nous fixons ici le niveau à 11.0, soit DirectX 11. Vous pouvez le mettre à 10 ou 9 pour utiliser une version de DirectX antérieure si vous prévoyez de prendre en charge plusieurs versions ou d'exécuter votre code sur une ancienne machine.

Sélectionnez
    // Règle le niveau de fonctionnalité de DirectX 11.
    featureLevel = D3D_FEATURE_LEVEL_11_0;

Maintenant que la description de la chaîne de permutation et le niveau de fonctionnalité ont été remplis, nous pouvons créer la chaîne de permutation, le dispositif Direct3D, et le contexte de périphérique Direct3D. Ces deux derniers sont très importants, ils sont l'interface de toutes les fonctions Direct3D. Nous les utiliserons pour presque tout par la suite.

Ceux d'entre vous qui connaissent les versions précédentes de DirectX reconnaîtront le dispositif Direct3D, mais découvriront le nouveau contexte de périphérique Direct3D. Fondamentalement, ils ont pris les fonctionnalités du dispositif Direct3D et les ont scindées en deux périphériques différents. Vous aurez donc besoin d'utiliser les deux.

Notez que si l'utilisateur ne dispose pas d'une carte vidéo DirectX 11, l'appel de cette fonction ne parviendra pas à créer le dispositif et son contexte. Aussi, si vous souhaitez tout de même tester les fonctionnalités DirectX 11, vous pouvez remplacer D3D_DRIVER_TYPE_HARDWARE par D3D_DRIVER_TYPE_REFERENCE. DirectX utilisera votre CPU au lieu de la carte vidéo pour dessiner. Notez que cela prend 1/1000 de la vitesse, mais c'est pratique pour les gens qui n'ont pas encore de carte vidéo DirectX 11 sur toutes leurs machines.

Sélectionnez
    // Crée la chaîne de permutation, le périphérique Direct3D et le contexte de périphérique Direct3D.
    result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,
                           D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
    if(FAILED(result))
    {
        return false;
    }

Parfois, cet appel servant à créer le dispositif échouera si la carte vidéo principale n'est pas compatible avec DirectX 11. Certaines machines peuvent avoir la carte vidéo principale en DirectX 10 et la secondaire en DirectX 11. Certaines cartes graphiques hybrides fonctionnent de cette façon : avec la carte Intel principale ayant une plus faible puissance et la secondaire Nvidia plus puissante. Pour contourner ce problème, vous ne devrez pas utiliser le périphérique par défaut, mais énumérer toutes les cartes vidéo de la machine et demander à l'utilisateur laquelle il souhaite utiliser, puis spécifier cette carte lors de la création du dispositif.
Maintenant que nous avons une chaîne de permutation, nous devons obtenir un pointeur vers le tampon arrière et le lier avec elle en utilisant la fonction CreateRenderTargetView.

Sélectionnez
    // Obtient le pointeur vers le tampon arrière.
    result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
    if(FAILED(result))
    {
        return false;
    }

    // Crée le rendu de la cible à partir du tampon arrière.
    result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
    if(FAILED(result))
    {
        return false;
    }

    // Libère le pointeur vers le tampon arrière, car nous n'en avons plus besoin.
    backBufferPtr->Release();
    backBufferPtr = 0;

Nous aurons également besoin de mettre en place la description du tampon de profondeur, que nous utiliserons pour avoir un rendu 3D correct de nos polygones. En même temps, nous allons lui lier un tampon stencil. Le tampon stencil peut être utilisé pour obtenir des effets de flou de mouvement, d'ombres volumétriques et autres.

Sélectionnez
    // Initialise la description du tampon de profondeur.
    ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

    // Définit la description du tampon de profondeur.
    depthBufferDesc.Width = screenWidth;
    depthBufferDesc.Height = screenHeight;
    depthBufferDesc.MipLevels = 1;
    depthBufferDesc.ArraySize = 1;
    depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthBufferDesc.SampleDesc.Count = 1;
    depthBufferDesc.SampleDesc.Quality = 0;
    depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthBufferDesc.CPUAccessFlags = 0;
    depthBufferDesc.MiscFlags = 0;

Maintenant, nous créons les tampons de profondeur/stencil en utilisant cette description. Vous remarquerez que nous utilisons la fonction CreateTexture2D pour créer les tampons, de ce fait le tampon est juste une texture 2D. La raison en est que, une fois que vos polygones sont triés puis pixelisés, ils finissent par être des pixels de couleur dans ce tampon 2D. Ce tampon 2D est alors dessiné à l'écran.

Sélectionnez
    // Crée la texture pour le tampon de profondeur en utilisant la description précédemment définie.
    result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
    if(FAILED(result))
    {
        return false;
    }

Maintenant, nous devons configurer la description du stencil de profondeur. Cela nous permet de contrôler le type de test de profondeur qu'effectuera Direct3D pour chaque pixel.

Sélectionnez
    // Initialise la description de l'état stencil.
    ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

    // Configure la description de l'état stencil.
    depthStencilDesc.DepthEnable = true;
    depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
    depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

    depthStencilDesc.StencilEnable = true;
    depthStencilDesc.StencilReadMask = 0xFF;
    depthStencilDesc.StencilWriteMask = 0xFF;

    // Opérations sur le tampon stencil si le pixel est de face.
    depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
    depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
    depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
    depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

    // Opérations sur le tampon stencil si le pixel est situé à l'arrière.
    depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
    depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
    depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
    depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

La description renseignée, nous pouvons créer un état de tampon profondeur/stencil.

Sélectionnez
    // Crée l'état du tampon de profondeur/stencil.
    result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
    if(FAILED(result))
    {
        return false;
    }

Avec l'état du tampon de profondeur/stencil créé, nous pouvons le régler pour qu'il prenne effet. Remarquez que nous utilisons le contexte de périphérique pour cela.

Sélectionnez
    // Définit l'état du tampon profondeur/stencil.
    m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

Nous avons ensuite besoin de créer la description de la vue du tampon profondeur/stencil. Nous faisons cela afin que Direct3D utilise le tampon de profondeur comme une texture de profondeur et de stencil. Après avoir renseigné la description, nous appelons la fonction CreateDepthStencilView pour créer notre vue.

Sélectionnez
    // Initialise la vue du tampon profondeur/stencil.
    ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

    // Configure la description de la vue du tampon profondeur/Stencil.
    depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    depthStencilViewDesc.Texture2D.MipSlice = 0;

    // Crée la vue du tampon profondeur/stencil.
    result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
    if(FAILED(result))
    {
        return false;
    }

Celle-ci créée, nous pouvons maintenant appeler OMSetRenderTargets qui va lier la vue cible de rendu et le tampon profondeur/stencil au pipeline de rendu de sortie. De cette façon, les graphismes rendus par le pipeline seront dessinés dans le tampon arrière que nous avons créé précédemment. Les graphiques écrits dans le tampon arrière, nous pouvons échanger ce dernier avec le tampon avant et afficher nos graphiques sur l'écran de l'utilisateur.

Sélectionnez
    // Lie la vue cible de rendu et le tampon de profondeur/stencil au pipeline de rendu de sortie.
    m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

Maintenant que les cibles de rendu sont configurées, nous pouvons considérer quelques fonctions supplémentaires qui nous donneront plus de contrôle sur nos scènes dans les tutoriels à venir. Premièrement, nous allons créer un état de rastérisation qui nous donnera le contrôle sur le rendu des polygones. Par exemple, faire que nos scènes soient rendues en mode filaire ou faire dessiner à DirectX les deux faces avant et arrière des polygones. Par défaut, DirectX a déjà un état de rastérisation mis en place qui permet de faire exactement ce que fait le code qui suit, mais sans la possibilité de le modifier, vous devez pour cela le configurer vous-même.

Sélectionnez
    // Configure la description de trame qui va déterminer de quelle façon et quels polygones seront dessinés.
    rasterDesc.AntialiasedLineEnable = false;
    rasterDesc.CullMode = D3D11_CULL_BACK;
    rasterDesc.DepthBias = 0;
    rasterDesc.DepthBiasClamp = 0.0f;
    rasterDesc.DepthClipEnable = true;
    rasterDesc.FillMode = D3D11_FILL_SOLID;
    rasterDesc.FrontCounterClockwise = false;
    rasterDesc.MultisampleEnable = false;
    rasterDesc.ScissorEnable = false;
    rasterDesc.SlopeScaledDepthBias = 0.0f;

    // Crée l'état de rastérisation selon la description précédente.
    result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
    if(FAILED(result))
    {
        return false;
    }

    // Définit l'état de rasterisation.
    m_deviceContext->RSSetState(m_rasterState);

Le viewport doit aussi être configuré afin que Direct3D puisse lier l'espace du clip à l'espace cible de rendu. Réglez-le à la taille totale de la fenêtre.

Sélectionnez
    // Configure le viewport pour le rendu.
    viewport.Width = (float)screenWidth;
    viewport.Height = (float)screenHeight;
    viewport.MinDepth = 0.0f;
    viewport.MaxDepth = 1.0f;
    viewport.TopLeftX = 0.0f;
    viewport.TopLeftY = 0.0f;

    // Crée le viewport.
    m_deviceContext->RSSetViewports(1, &viewport);

Maintenant, nous allons créer la matrice de projection utilisée pour transformer la scène 3D dans le viewport 2D que nous avons créé précédemment. Nous aurons besoin de conserver une copie de cette matrice afin que nous puissions la transmettre à nos shaders utilisés pour le rendu de nos scènes.

Sélectionnez
    // Configure la matrice de projection.
    fieldOfView = (float)D3DX_PI / 4.0f;
    screenAspect = (float)screenWidth / (float)screenHeight;

    // Crée la matrice de projection pour le rendu 3D.
    D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);

Nous allons créer une autre matrice appelée matrice monde, utilisée pour convertir les sommets de nos objets en sommets dans la scène 3D. Cette matrice sera également utilisée pour la rotation, la translation et le redimensionnement de nos objets dans l'espace 3D. Au début, nous allons juste l'initialiser à la matrice identité et conserver une copie dans notre instance. Nous aurons besoin de transmettre cette copie aux shaders, également pour le rendu.

Sélectionnez
    // Initialise la matrice monde à la matrice identité.
    D3DXMatrixIdentity(&m_worldMatrix);

Généralement, c'est là que vous créez une matrice de vue utilisée pour calculer la position de l'endroit d'où nous observons la scène. Vous pouvez voir cela comme une scène que l'on regarde à travers un appareil photo. Pour cela, je créerai dans les tutoriels à venir une classe Camera logiquement utilisée ici. Nous sautons cette étape pour l'instant.

Dernière chose à mettre en place dans la fonction Initialize : une matrice de projection orthographique. Cette matrice est utilisée pour le rendu d'éléments, telles des interfaces utilisateur, en 2D sur l'écran qui nous permettent de sauter le rendu 3D. Elle sera utilisée dans les futurs tutoriels lorsque nous aborderons le rendu des graphiques 2D et des polices à l'écran.

Sélectionnez
    //Crée une matrice de projection orthographique pour le rendu 2D.
    D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);

    return true;
}

La fonction Shutdown va libérer et nettoyer tous les pointeurs utilisés dans la fonction Initialize, c'est plutôt direct. Mais avant cela, j'effectue un appel pour forcer la chaîne de permutation à passer en mode fenêtré avant de libérer un seul pointeur. Si vous ne faites pas cela et que vous essayez de libérer la chaîne de permutation en mode plein écran, des exceptions seront lancées. Pour éviter que cela ne se produise, nous forçons toujours le mode fenêtré avant d'arrêter Direct3D.

Sélectionnez
void D3DClass::Shutdown()
{
    // Avant l'arrêt, on met en mode fenêtré sinon lors de la libération de la chaîne de permutation, une exception sera lancée.
    if(m_swapChain)
    {
        m_swapChain->SetFullscreenState(false, NULL);
    }

    if(m_rasterState)
    {
        m_rasterState->Release();
        m_rasterState = 0;
    }

    if(m_depthStencilView)
    {
        m_depthStencilView->Release();
        m_depthStencilView = 0;
    }

    if(m_depthStencilState)
    {
        m_depthStencilState->Release();
        m_depthStencilState = 0;
    }

    if(m_depthStencilBuffer)
    {
        m_depthStencilBuffer->Release();
        m_depthStencilBuffer = 0;
    }

    if(m_renderTargetView)
    {
        m_renderTargetView->Release();
        m_renderTargetView = 0;
    }

    if(m_deviceContext)
    {
        m_deviceContext->Release();
        m_deviceContext = 0;
    }

    if(m_device)
    {
        m_device->Release();
        m_device = 0;
    }

    if(m_swapChain)
    {
        m_swapChain->Release();
        m_swapChain = 0;
    }

    return;
}

Dans la classe D3DClass, j'ai quelques fonctions d'assistance. Les deux premières sont BeginScene et EndScene. BeginScene sera appelée chaque fois que nous dessinerons une nouvelle scène 3D au début de chaque trame. Tout ce qu'elle fait est initialiser les tampons de sorte qu'ils soient vides et prêts à être dessinés. L'autre fonction, EndScene, demande à la chaîne de permutation d'afficher notre scène 3D une fois que le dessin est terminé, à la fin de chaque trame.

Sélectionnez
void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
    float color[4];

    // Fixe la couleur d'effacement du tampon.
    color[0] = red;
    color[1] = green;
    color[2] = blue;
    color[3] = alpha;

    // Efface le tampon arrière.
    m_deviceContext->ClearRenderTargetView(m_renderTargetView, color);

    // Efface le tampon de profondeur.
    m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

    return;
}

void D3DClass::EndScene()
{
    // Affiche le tampon arrière à l'écran dès que le rendu est terminé.
    if(m_vsync_enabled)
    {
        // Synchronise à la fréquence de rafraîchissement de l'écran.
        m_swapChain->Present(1, 0);
    }
    else
    {
        // Affichage le plus rapidement possible.
        m_swapChain->Present(0, 0);
    }

    return;
}

Les fonctions qui suivent récupèrent simplement des pointeurs vers le dispositif et le contexte de périphérique de Direct3D. Elles seront souvent appelées par le cadre de travail.

Sélectionnez
ID3D11Device* D3DClass::GetDevice()
{
    return m_device;
}

ID3D11DeviceContext* D3DClass::GetDeviceContext()
{
    return m_deviceContext;
}

Les trois fonctions suivantes donnent des copies des matrices de projection, monde, et orthographique aux fonctions appelantes. La plupart des shaders auront besoin de ces matrices pour effectuer le rendu, les objets extérieurs ont donc besoin d'un moyen facile d'obtenir une copie. Nous n'appellerons pas ces fonctions dans ce tutoriel, j'explique juste leur présence dans le code.

Sélectionnez
void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix)
{
    projectionMatrix = m_projectionMatrix;
    return;
}

void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix)
{
    worldMatrix = m_worldMatrix;
    return;
}

void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix)
{
    orthoMatrix = m_orthoMatrix;
    return;
}

Les dernières fonctions retournent par référence le nom de la carte vidéo et sa quantité de mémoire dédiée. Connaître ces informations peut aider au débogage sur des configurations différentes.

Sélectionnez
void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
    strcpy_s(cardName, 128, m_videoCardDescription);
    memory = m_videoCardMemory;
    return;
}

VII. Résumé

Nous sommes enfin en mesure d'initialiser et stopper Direct3D ainsi que d'effectuer le rendu d'une couleur dans la fenêtre. Compiler et exécuter le code va produire la même fenêtre que le dernier tutoriel, mais Direct3D est ici initialisé et la fenêtre est effacée à la couleur grise. Il vous indiquera également si votre compilateur est correctement configuré et peut voir les en-têtes et les fichiers de bibliothèque du SDK de DirectX.

VII-A. Exercices à faire

  1. Recompiler le code et exécuter le programme pour s'assurer du fonctionnement de DirectX, sinon regarder les étapes du premier tutoriel. Appuyer sur la touche Echap pour quitter le programme après l'affichage de la fenêtre ;
  2. Modifier la variable globale dans le fichier graphicsclass.h pour activer le plein écran et recompiler/exécuter ;
  3. Remplacer la couleur d'effacement dans la méthode GraphicsClass::Rendu par du jaune ;
  4. Écrire le nom de la carte vidéo et sa mémoire dédiée dans un fichier texte.

VIII. Code source

  • Projet Visual Studio 2010 : dx11tut03.zip
  • Source seule : dx11src03.zip
  • Exécutable seul : dx11exe03.zip

IX. Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek

Copyright © 2000-2015 - www.developpez.com


ASM Mode Reel

DirectX 11 - Tutoriel 4 : tampons, shaders et HLSL

Table des matières

  • Navigation
  • II. Tampons de sommets
  • III. Tampons d'indices
  • IV. Vertex Shaders
  • V. Pixel Shaders
  • VI. HLSL
  • VII. Mise à jour du cadre de travail
  • VIII. Color.vs
  • IX. Color.ps
  • X. Modelclass.h
  • XI. Modelclass.cpp
  • XII. Colorshaderclass.h
  • XIII. Colorshaderclass.cpp
  • XIV. Cameraclass.h
  • XV. Cameraclass.cpp
  • XVI. Graphicsclass.h
  • XVII. Graphicsclass.cpp
  • XVIII. Résumé
  • XIX. Exercices à faire
  • XX. Code source
  • XXI. Remerciements
  • Navigation

Ce tutoriel introduit l'écriture des vertex et pixel shaders ainsi que l'utilisation des tampons de sommets et d'indices dans DirectX 11. Ce sont les concepts les plus fondamentaux que vous devez comprendre et utiliser pour effectuer un rendu 3D.

II. Tampons de sommets

Le premier élément à comprendre concerne les tampons de sommets. Pour illustrer ce concept, prenons l'exemple d'un modèle 3D d'une sphère :

Image non disponible

Le modèle de la sphère 3D est en fait composé de centaines de triangles :

Image non disponible

Chacun des triangles dans le modèle de la sphère possède trois points, que l'on appelle sommets. Donc, pour effectuer le rendu du modèle de la sphère, nous devons stocker tous les sommets qui forment la sphère, dans un tableau de données spécial que nous appelons un tampon de sommets. Une fois tous les points du modèle de la sphère dans le tampon de sommets, nous pouvons l'envoyer au GPU afin qu'il puisse réaliser le rendu du modèle.

III. Tampons d'indices

Les tampons d'indices sont liés aux tampons de sommets. Leur but est d'enregistrer la position de chaque sommet du tampon de sommets. Le GPU utilise alors le tampon d'indices pour retrouver rapidement un sommet. Le concept du tampon d'indices est similaire au concept de l'index dans un livre, il permet de trouver la rubrique que vous recherchez plus rapidement. La documentation du SDK de DirectX précise que l'utilisation de tampons d'indices peut également augmenter la possibilité de mettre en cache les données des sommets dans des emplacements plus rapides de la mémoire vidéo. Il est donc fortement conseillé de les utiliser, également pour des raisons de performances.

IV. Vertex Shaders

Les vertex shaders sont de petits programmes écrits principalement pour transformer les sommets du tampon de sommets vers l'espace 3D. D'autres calculs tels que celui des normales en chaque sommet peuvent être effectués. Le programme du vertex shader sera appelé par le GPU pour chaque sommet à traiter. Par exemple, un modèle de 5 000 polygones exécutera votre programme vertex shader 15 000 fois lors de chaque trame pour ce modèle unique. Donc, si vous verrouillez votre programme graphique à 60 fps, il appellera votre vertex shader 900 000 fois par seconde pour seulement dessiner 5 000 triangles. Comme vous pouvez le deviner, écrire des vertex shaders performants est important.

V. Pixel Shaders

Les pixel shaders sont de petits programmes écrits pour faire la coloration des polygones que nous dessinons. Ils sont exécutés par le GPU pour chaque pixel visible dessiné à l'écran. Le coloriage, l'application de textures, l'éclairage et la plupart des autres effets que vous pourrez appliquer aux faces de vos polygones sont gérés par ces programmes. Les pixel shaders doivent être écrits efficacement en raison du nombre de fois qu'ils seront appelés par le GPU.

VI. HLSL

HLSL est le langage que nous utilisons dans DirectX 11 pour coder les pixels et vertex shaders. La syntaxe est à peu près identique au langage C avec certains types pré-définis. Les fichiers HLSL sont composés de variables globales, définitions de types, vertex shaders, pixel shaders et geometry shaders. Comme ceci est le premier tutoriel HLSL, nous ferons pour commencer un programme HLSL très simple utilisant DirectX 11.

VII. Mise à jour du cadre de travail

Image non disponible

Le cadre de travail a été mis à jour pour ce tutoriel. Dans la classe GraphicsClass, nous avons ajouté trois nouvelles classes appelées CameraClass, ModelClass et ColorShaderClass. La classe CameraClass s'occupera de la matrice de vue dont nous avons parlé précédemment. Elle se chargera de la position de la caméra dans le monde et de sa transmission aux shaders lorsqu'ils auront besoin de dessiner ou savoir d'où nous regardons la scène. La classe ModelClass se chargera de la géométrie de nos modèles 3D. Dans ce tutoriel, le modèle 3D se résumera à un seul triangle pour des raisons de simplicité. Et finalement ColorShaderClass sera responsable du rendu du modèle à l'écran, en invoquant notre shader HLSL.

Nous allons commencer le tutoriel en regardant le code des programmes de shaders HLSL.

VIII. Color.vs

Ceux-ci seront nos premiers programmes de shader. Les shaders sont de petits programmes qui font le rendu réel de modèles. Ces shaders sont écrits en HLSL et stockés dans les fichiers sources nommés color.vs et color.ps. J'ai placé les fichiers d'extension .cpp et .h dans le moteur pour le moment. Le but de ce shader est de juste dessiner des triangles de couleur, gardant les choses aussi simples que possible dans ce premier tutoriel HLSL. Premièrement, voici le code pour le vertex shader :

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : color.vs
////////////////////////////////////////////////////////////////////////////////

Les programmes shader débutent avec les variables globales. Ces variables globales peuvent être modifiées à l'extérieur de votre code C++. Vous pouvez utiliser de nombreux types de variables tels que int ou float, les définir à l'extérieur et les utiliser dans vos programmes de shader. En général, vous allez mettre la plupart de ces variables globales dans des objets tampons appelés « cbuffer » même s'il n'est question que d'une seule variable. L'organisation logique de ces tampons est importante pour l'exécution efficace des shaders ainsi que la façon dont la carte graphique va stocker ces tampons. Dans cet exemple, j'ai mis trois matrices dans le même tampon puisque je vais les mettre à jour en même temps à chaque trame.

Sélectionnez
////////////////////////
// VARIABLES GLOBALES //
////////////////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

De même qu'en C, nous pouvons créer nos propres définitions de type. Nous allons utiliser différents types tels que float4 disponible en HLSL, qui rendent la programmation de shaders plus facile et lisible. Dans cet exemple, nous créons des types qui ont les vecteurs de position x, y, z, w et les couleurs : rouge, vert, bleu et alpha. POSITION, COLOR et SV_POSITION sont des sémantiques qui renseignent le GPU sur l'utilisation des variables. Je dois créer deux structures différentes ici car les sémantiques pour les vertex et pixel shaders sont différentes, même si leurs structures sont identiques au fond. POSITION fonctionne pour les vertex shaders et SV_POSITION fonctionne pour les pixel shaders ; COLOR fonctionne pour les deux. Si vous en voulez plusieurs de même type, vous devez ajouter un numéro à la fin tel que COLOR0, COLOR1, et ainsi de suite.

Sélectionnez
//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};

Le vertex shader est appelé par le GPU lorsqu'il traite les données qui lui ont été envoyées par les tampons de sommets. Ce vertex shader que j'ai nommé ColorVertexShader sera appelé pour chaque sommet contenu dans le tampon de sommets. L'entrée du vertex shader doit correspondre au format de données du tampon de sommets ainsi qu'à la définition du type dans le fichier source du shader qui est ici VertexInputType. La sortie du vertex shader sera envoyée au pixel shader. Dans notre cas, le type de sortie se nomme PixelInputType tel que vous le voyez défini ci-dessus.

Avec cela à l'esprit, vous voyez que le vertex shader crée une variable de sortie de type PixelInputType. Il prend alors la position du sommet en entrée et la multiplie par les matrices monde, de vue et de projection. Ceci placera le sommet au bon emplacement pour le rendu dans l'espace 3D selon notre point de vue, puis sur l'écran 2D. Ensuite, la variable de sortie récupère une copie de la couleur d'entrée et est renvoyée en entrée du pixel shader. Notez également que je définis la valeur W de la position d'entrée à 1.0 car ne lisant que le vecteur XYZ, sa valeur n'est pas définie.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ColorVertexShader(VertexInputType input)
{
    PixelInputType output;

    // Change la quatrième composante du vecteur pour avoir des calculs matriciels corrects.
    input.position.w = 1.0f;

    // Calcule la position du sommet selon les matrices monde, de vue et de projection.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

    // Stocke la couleur d'entrée pour le pixel shader.
    output.color = input.color;

    return output;
}

IX. Color.ps

Le pixel shader dessine chaque pixel sur les polygones qui seront affichés à l'écran. Ce pixel shader utilise un PixelInputType en entrée et renvoie un float4 (représentant la couleur finale du pixel) en sortie. Ce programme de pixel shader est très simple : nous lui demandons juste de recopier la couleur d'entrée. Notez que le pixel shader prend son entrée en sortie du vertex shader.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : color.ps
////////////////////////////////////////////////////////////////////////////////

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
    return input.color;
}

X. Modelclass.h

Comme indiqué précédemment, la classe ModelClass est responsable de l'encapsulation de la géométrie des modèles 3D. Dans ce tutoriel, nous allons définir manuellement les données pour un seul triangle vert. Nous allons également créer pour ce triangle les tampons de sommets et d'indices, de sorte qu'il puisse être affiché.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:

Voici la définition de notre type de sommet qui sera utilisé avec le tampon de sommets dans cette classe ModelClass. Notez également que la disposition des éléments doit correspondre à celle de la classe ColorShaderClass que nous verrons plus tard dans le tutoriel.

Sélectionnez
    struct VertexType
    {
        D3DXVECTOR3 position;
        D3DXVECTOR4 color;
    };

public:
    ModelClass();
    ModelClass(const ModelClass&);
    ~ModelClass();

Ces fonctions gèrent l'initialisation et la finalisation des tampons de sommets et d'indices du modèle. La fonction Render envoie la géométrie du modèle vers la carte vidéo afin de préparer son affichage qui sera effectué par le shader de couleur.

Sélectionnez
    bool Initialize(ID3D11Device*);
    void Shutdown();
    void Render(ID3D11DeviceContext*);

    int GetIndexCount();

private:
    bool InitializeBuffers(ID3D11Device*);
    void ShutdownBuffers();
    void RenderBuffers(ID3D11DeviceContext*);

Les attributs privés dans la classe ModelClass sont les tampons de sommets et d'indices ainsi que deux entiers gardant la taille de chaque tampon. Notez que les tampons sous DirectX 11 utilisent principalement le type générique ID3D11Buffer et sont plus clairement identifiés par une description de tampon lors de leur création.

Sélectionnez
private:
    ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
    int m_vertexCount, m_indexCount;
};

#endif

XI. Modelclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"

Le constructeur de la classe initialise à NULL les pointeurs sur les tampons des sommets et d'indices.

Sélectionnez
ModelClass::ModelClass()
{
    m_vertexBuffer = 0;
    m_indexBuffer = 0;
}

ModelClass::ModelClass(const ModelClass& other)
{
}

ModelClass::~ModelClass()
{
}

La méthode Initialize va appeler les fonctions d'initialisation des tampons de sommets et d'indices.

Sélectionnez
bool ModelClass::Initialize(ID3D11Device* device)
{
    bool result;

    // Initialise les tampons d'indices et de sommets qui contiendront la géométrie du triangle.
    result = InitializeBuffers(device);
    if(!result)
    {
        return false;
    }

    return true;
}

La méthode Shutdown va appeler les fonctions de libération des tampons de sommets et d'indices.

Sélectionnez
void ModelClass::Shutdown()
{
    // Libère les tampons d'indices et de sommets.
    ShutdownBuffers();

    return;
}

La méthode Render est appelée dans la méthode GraphicsClass::Render. Cette fonction appelle la fonction RenderBuffers pour placer les tampons de sommets et d'indices sur le pipeline graphique afin que le shader de couleur puisse effectuer leur rendu.

Sélectionnez
void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
    // Place les tampons d'indices et de sommets sur le pipeline graphique afin de préparer leur rendu.
    RenderBuffers(deviceContext);

    return;
}

La fonction GetIndexCount retourne le nombre d'indices du modèle. Le shader de couleur aura besoin de cette information pour le dessiner.

Sélectionnez
int ModelClass::GetIndexCount()
{
    return m_indexCount;
}

La fonction InitializeBuffers sert à la création des tampons de sommets et d'indices. Généralement, vous effectuerez la lecture d'un modèle depuis un fichier et créerez les tampons à partir des données stockées. Ne dessinant qu'un triangle dans ce tutoriel, nous écrirons manuellement les sommets et indices.

Sélectionnez
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
    VertexType* vertices;
    unsigned long* indices;
    D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D11_SUBRESOURCE_DATA vertexData, indexData;
    HRESULT result;

Nous créons tout d'abord deux tableaux temporaires pour stocker les sommets et indices qui vont nous aider à remplir les tampons définitifs.

Sélectionnez
    // Fixe la taille du tableau de sommets.
    m_vertexCount = 3;

    // Fixe la taille du tableau d'indices.
    m_indexCount = 3;

    // Crée le tableau de sommets.
    vertices = new VertexType[m_vertexCount];
    if(!vertices)
    {
        return false;
    }

    // Crée le tableau d'indices.
    indices = new unsigned long[m_indexCount];
    if(!indices)
    {
        return false;
    }

Puis, remplissons à la fois le sommet et le tableau d'indices avec les trois points du triangle, ainsi que l'indice de chacun de ces points. Notez que je crée les points pour les dessiner dans le sens horaire. Si vous faites cela dans le sens antihoraire, le résultat sera un triangle orienté en sens inverse et qui ne sera pas dessiné à cause de la suppression des faces arrières (back face culling). Toujours se rappeler que l'ordre dans lequel vous envoyez vos sommets au GPU est très important. La couleur est aussi définie ici, car elle fait partie de la description du sommet. J'ai mis la couleur verte.

Sélectionnez
    // Charge les données dans le tableau de sommets.
    vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // En bas à gauche.
    vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

    vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // En haut au milieu
    vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

    vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // En bas à droite.
    vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

    // Charge les données dans le tableau d'indices.
    indices[0] = 0;  // En bas à gauche.
    indices[1] = 1;  // En haut au milieu
    indices[2] = 2;  // En bas à droite.

Une fois les tableaux de sommets et d'indices remplis, nous pouvons les utiliser pour créer les tampons. Pour cela, nous réutilisons la même méthode. Premièrement, le remplissage de la description du tampon, et vous devez vous assurer que les deux champs ByteWidth (taille du tampon) et BindFlags (type de tampon) soient remplis correctement. Une fois la description renseignée, vous devez également renseigner un pointeur de sous-ressource qui ira pointer sur l'un des deux tampons de sommets ou d'indices créés. Vous pouvez alors appeler CreateBuffer en utilisant l'objet D3D qui retournera un pointeur vers votre nouveau tampon.

Sélectionnez
    // Met en place la description du tampon statique de sommets.
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
    vertexBufferDesc.StructureByteStride = 0;

    // Donne à la structure de sous-ressources un pointeur vers le tableau des sommets.
    vertexData.pSysMem = vertices;
    vertexData.SysMemPitch = 0;
    vertexData.SysMemSlicePitch = 0;

    // Crée le tampon des sommets.
    result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
    if(FAILED(result))
    {
        return false;
    }

    // Met en place la description du tampon statique des indices.
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
    indexBufferDesc.StructureByteStride = 0;

    // Donne à la structure de sous-ressources un pointeur vers le tableau des indices.
    indexData.pSysMem = indices;
    indexData.SysMemPitch = 0;
    indexData.SysMemSlicePitch = 0;

    // Crée le tampon des indices.
    result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
    if(FAILED(result))
    {
        return false;
    }

Les deux tampons créés, vous pouvez libérer les tableaux qui ne nous serviront plus, puisque les données ont été copiées dans les tampons.

Sélectionnez
    // Libère les tableaux, maintenant que les tampons ont été créés et remplis.
    delete [] vertices;
    vertices = 0;

    delete [] indices;
    indices = 0;

    return true;
}

La méthode ShutdownBuffers libère juste les deux tampons ainsi créés dans la méthode InitializeBuffers.

Sélectionnez
void ModelClass::ShutdownBuffers()
{
    // Libère le tampon d'indices.
    if(m_indexBuffer)
    {
        m_indexBuffer->Release();
        m_indexBuffer = 0;
    }

    //  Libère le tampon de sommets.
    if(m_vertexBuffer)
    {
        m_vertexBuffer->Release();
        m_vertexBuffer = 0;
    }

    return;
}

La méthode RenderBuffers est appelée dans la méthode Render. Son but est d'activer les tampons de sommets et d'indices sur l'assembleur d'entrée (input assembler) dans le GPU. Une fois que le GPU a un tampon de sommets actif, il peut dès lors utiliser le shader pour effectuer son rendu. Cette fonction définit également la façon dont ces tampons doivent être dessinés : en triangles, lignes, éventails, etc. Dans ce tutoriel, nous avons activés les tampons de sommets et d'indices sur l'assembleur d'entrée et signalé au GPU qu'ils doivent être dessinés à l'aide de triangles avec la fonction IASetPrimitiveTopology de DirectX.

Sélectionnez
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
    unsigned int stride;
    unsigned int offset;

    // Règle le pas et l'offset du tampon.
    stride = sizeof(VertexType);
    offset = 0;

    // Active le tampon des sommets dans l'assembleur d'entrée pour effectuer son rendu.
    deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

    // Active le tampon des indices dans  l'assembleur d'entrée pour effectuer son rendu.
    deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

    // Définit le type de primitive qui doit être rendue depuis ce tampon de sommets, ici un triangle.
    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    return;
}

XII. Colorshaderclass.h

Nous utilisons la classe ColorShaderClass pour invoquer nos shaders HLSL afin de dessiner nos modèles 3D dans le GPU.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : colorshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _COLORSHADERCLASS_H_
#define _COLORSHADERCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ColorShaderClass
////////////////////////////////////////////////////////////////////////////////
class ColorShaderClass
{
private:

Voici la définition du type cBuffer qui sera utilisé avec le vertex shader. Cette définition doit être exactement la même que celle du vertex shader, car les données du modèle doivent correspondre aux définitions des types du shader pour un rendu correct.

Sélectionnez
    struct MatrixBufferType
    {
        D3DXMATRIX world;
        D3DXMATRIX view;
        D3DXMATRIX projection;
    };

public:
    ColorShaderClass();
    ColorShaderClass(const ColorShaderClass&);
    ~ColorShaderClass();

Les méthodes ci-dessous gèrent l'initialisation et l'arrêt du shader. La méthode Render définit les paramètres du shader puis dessine les sommets des modèles préparés.

Sélectionnez
    bool Initialize(ID3D11Device*, HWND);
    void Shutdown();
    bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);

private:
    bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
    void ShutdownShader();
    void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

    bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
    void RenderShader(ID3D11DeviceContext*, int);

private:
    ID3D11VertexShader* m_vertexShader;
    ID3D11PixelShader* m_pixelShader;
    ID3D11InputLayout* m_layout;
    ID3D11Buffer* m_matrixBuffer;
};

#endif

XIII. Colorshaderclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : colorshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "colorshaderclass.h"

Comme d'habitude, nous initialisons tous les pointeurs private à NULL.

Sélectionnez
ColorShaderClass::ColorShaderClass()
{
    m_vertexShader = 0;
    m_pixelShader = 0;
    m_layout = 0;
    m_matrixBuffer = 0;
}

ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}

ColorShaderClass::~ColorShaderClass()
{
}

La méthode Initialize appelle la fonction d'initialisation des shaders. Nous lui passons le nom des fichiers de shaders HLSL ; dans ce tutoriel, leur nom sont color.vs et color.ps.

Sélectionnez
bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
    bool result;

    // Initialise les vertex et pixel shaders.
    result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps");
    if(!result)
    {
        return false;
    }

    return true;
}

La méthode Shutdown appelle la fonction de fermeture des shaders.

Sélectionnez
void ColorShaderClass::Shutdown()
{
    // Ferme les vertex et pixel shaders ainsi que les objets associés.
    ShutdownShader();

    return;
}

La méthode Render va d'abord définir les paramètres à l'intérieur du shader à l'aide la fonction SetShaderParameters. Ensuite, elle appelle la fonction RenderShader, pour dessiner le triangle de couleur verte en utilisant le shader HLSL.

Sélectionnez
bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix,
                  D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
    bool result;

    // Définit les paramètres du shader qui seront utilisés pour le rendu.
    result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);
    if(!result)
    {
        return false;
    }

    // Effectue le rendu des tampons avec le shader.
    RenderShader(deviceContext, indexCount);

    return true;
}

Nous allons maintenant commencer par l'une des plus importantes fonctions de ce tutoriel : InitializeShader. Cette fonction a pour but de charger les fichiers du shader et de les rendre utilisables par DirectX et le GPU. Vous pouvez également voir la configuration de la disposition du shader et la façon dont les données du tampon de sommets serviront à la lecture sur le pipeline graphique dans le GPU. La disposition du shader devra correspondre au VertexType situé dans le fichier modelclass.h ainsi qu'à celui défini dans le fichier color.vs.

Sélectionnez
bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
    HRESULT result;
    ID3D10Blob* errorMessage;
    ID3D10Blob* vertexShaderBuffer;
    ID3D10Blob* pixelShaderBuffer;
    D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
    unsigned int numElements;
    D3D11_BUFFER_DESC matrixBufferDesc;

    // Initialise à zéro les pointeurs que la méthode va utiliser.
    errorMessage = 0;
    vertexShaderBuffer = 0;
    pixelShaderBuffer = 0;

C'est ici que nous compilons les programmes shaders au sein des tampons. Nous fournissons le nom du fichier de shader, le nom du shader, sa version (5.0 pour DirectX 11), et le tampon où sera compilé le shader. Si la compilation échoue, le shader écrira un message d'erreur dans la chaîne errorMessage, que nous envoyons à une autre fonction afin d'écrire l'erreur. Si elle échoue mais qu'il n'y a pas de message dans errorMessage, cela signifie qu'il n'a pas pu trouver le fichier de shader, nous affichons alors une boîte de dialogue pour le notifier.

Sélectionnez
    // Compile le code du vertex shader.
    result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
                       &vertexShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
        }
        // Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
        else
        {
            MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

    // Compile le code du pixel shader.
    result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
                       &pixelShaderBuffer, &errorMessage, NULL);
    if(FAILED(result))
    {
        // Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
        if(errorMessage)
        {
            OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
        }
        // Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
        else
        {
            MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
        }

        return false;
    }

Une fois que le code du vertex shader et pixel shader a été compilé avec succès dans les tampons, nous utilisons ces derniers pour créer les objets shader eux-mêmes. Nous utiliserons ces pointeurs comme interface avec les vertex shader et pixel shader à partir de ce point.

Sélectionnez
    // Crée le vertex shader à partir du tampon.
    result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
    if(FAILED(result))
    {
        return false;
    }

    // Crée le pixel shader à partir du tampon.
    result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
    if(FAILED(result))
    {
        return false;
    }

L'étape suivante consiste à créer la disposition des données représentant les sommets qui seront traités par le shader. Comme ce shader utilise un vecteur de positions et de couleurs, nous devons les créer tous les deux dans cette disposition, en spécifiant leur taille. Le nom sémantique est le premier élément à mettre dans la disposition du shader, ce qui permet au shader de déterminer son utilisation. Comme nous avons deux éléments distincts, nous utilisons POSITION pour le premier et COLOR pour le second. La prochaine partie importante de la disposition est le format. Nous utilisons DXGI_FORMAT_R32G32B32_FLOAT pour le vecteur de positions et DXGI_FORMAT_R32G32B32A32_FLOAT pour la couleur. Pour finir, vous devez faire attention au champ AlignedByteOffset qui indique comment les données sont espacées dans le tampon. Pour cette disposition, nous disons que les douze premiers octets sont pour la position et les seize octets suivants sont pour la couleur. AlignedByteOffset désigne où chaque élément commence. Vous pouvez utiliser D3D11_APPEND_ALIGNED_ELEMENT au lieu de vos propres valeurs pour qu'il vous calcule automatiquement l'espacement. J'ai laissé les autres paramètres à leur valeur par défaut pour le moment, car nous ne nous en servons pas dans ce tutoriel.

Sélectionnez
    // Met en place la disposition des données transmises au shader.
    // Cette disposition doit être la même que celle de la structure VextexType du shader.
    polygonLayout[0].SemanticName = "POSITION";
    polygonLayout[0].SemanticIndex = 0;
    polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    polygonLayout[0].InputSlot = 0;
    polygonLayout[0].AlignedByteOffset = 0;
    polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[0].InstanceDataStepRate = 0;

    polygonLayout[1].SemanticName = "COLOR";
    polygonLayout[1].SemanticIndex = 0;
    polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    polygonLayout[1].InputSlot = 0;
    polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
    polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
    polygonLayout[1].InstanceDataStepRate = 0;

Une fois la description de la disposition des données effectuée, nous pouvons obtenir sa taille, puis la créer en utilisant l'objet D3D. Nous libérerons également les tampons des vertex shader et pixel shader car nous n'en avons plus besoin une fois la disposition créée.

Sélectionnez
    // Obtient le nombre d'éléments de la disposition des données.
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

    // Crée la disposition d'entrée du vertex.
    result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
                       vertexShaderBuffer->GetBufferSize(), &m_layout);
    if(FAILED(result))
    {
        return false;
    }

    // Libère les vertex et pixel shaders car nous n'en avons plus besoin.
    vertexShaderBuffer->Release();
    vertexShaderBuffer = 0;

    pixelShaderBuffer->Release();
    pixelShaderBuffer = 0;

La dernière chose à configurer pour utiliser le shader est un tampon constant. Comme vous l'avez vu pour le vertex shader, nous avons juste un tampon constant ; nous avons donc besoin d'en configurer un ici afin que nous puissions l'interfacer avec le shader. L'utilisation du tampon sera dynamique puisque nous le mettrons à jour à chaque trame. Les drapeaux de liaison indiquent que ce tampon sera constant. Les drapeaux d'accès au processeur doivent correspondre à l'utilisation, nous leur assignons donc D3D11_CPU_ACCESS_WRITE. Une fois que la description est remplie, nous pouvons créer l'interface du tampon constant puis l'utiliser pour accéder aux variables internes du shader en utilisant la fonction SetShaderParameters.

Sélectionnez
    // Met en place la description du tampon de matrice dynamique constant situé dans le vertex shader.
    matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
    matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    matrixBufferDesc.MiscFlags = 0;
    matrixBufferDesc.StructureByteStride = 0;

    // Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
    result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
    if(FAILED(result))
    {
        return false;
    }

    return true;
}

La méthode ShutdownShader libère les quatre interfaces configurées dans la méthode InitializeShader.

Sélectionnez
void ColorShaderClass::ShutdownShader()
{
    // Libère le tampon de matrice constant.
    if(m_matrixBuffer)
    {
        m_matrixBuffer->Release();
        m_matrixBuffer = 0;
    }

    // Libère la disposition des données.
    if(m_layout)
    {
        m_layout->Release();
        m_layout = 0;
    }

    // Libère le pixel shader.
    if(m_pixelShader)
    {
        m_pixelShader->Release();
        m_pixelShader = 0;
    }

    // Libère le vertex shader.
    if(m_vertexShader)
    {
        m_vertexShader->Release();
        m_vertexShader = 0;
    }

    return;
}

La méthode OutputShaderErrorMessage écrit les messages d'erreur générés lors de la compilation des vertex shaders ou des pixel shaders.

Sélectionnez
void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
    char* compileErrors;
    unsigned long bufferSize, i;
    ofstream fout;

    // Récupère le pointeur vers le message d'erreur du tampon.
    compileErrors = (char*)(errorMessage->GetBufferPointer());

    // Obtient la longueur du message.
    bufferSize = errorMessage->GetBufferSize();

    // Ouvre un fichier pour y écrire le message d'erreur.
    fout.open("shader-error.txt");

    // Écrit le message d'erreur.
    for(i=0; i<bufferSize; i++)
    {
        fout << compileErrors[i];
    }

    // Ferme le fichier.
    fout.close();

    // Libère le message d'erreur.
    errorMessage->Release();
    errorMessage = 0;

    // Affiche un message à l'écran pour avertir l'utilisateur qu'il doit vérifier le contenu du fichier pour y trouver les erreurs de compilation.
    MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

    return;
}

La méthode SetShaderVariables facilite le réglage des variables globales dans le shader. Les matrices utilisées sont d'abord créées dans la classe GraphicsClass, puis envoyées par cette méthode dans le vertex shader lors de l'appel à la méthode Render.

Sélectionnez
bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
                       D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
    HRESULT result;
    D3D11_MAPPED_SUBRESOURCE mappedResource;
    MatrixBufferType* dataPtr;
    unsigned int bufferNumber;

Assurez-vous de transposer vos matrices avant de les envoyer dans le shader, il s'agit d'une contrainte de DirectX 11.

Sélectionnez
    // Transpose les matrices afin de les préparer pour le shader.
    D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
    D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
    D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

Verrouillez le tampon m_matrixBuffer, définissez les nouvelles matrices en son sein, puis déverrouillez-le.

Sélectionnez
    // Pose un verrou sur le tampon constant afin que l'on puise écrire dedans.
    result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if(FAILED(result))
    {
        return false;
    }

    // Récupère le pointeur des données dans le tampon constant.
    dataPtr = (MatrixBufferType*)mappedResource.pData;

    // Copie la matrice dans le tampon constant.
    dataPtr->world = worldMatrix;
    dataPtr->view = viewMatrix;
    dataPtr->projection = projectionMatrix;

    // Déverrouille le tampon constant.
    deviceContext->Unmap(m_matrixBuffer, 0);

Maintenant, placez le tampon de matrices mis à jour dans le vertex shader HLSL.

Sélectionnez
    // Définit la position du tampon constant dans le vertex shader.
    bufferNumber = 0;

    // Fixe finalement le tampon constant contenant les données à jour dans le vertex shader.
    deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

    return true;
}

RenderShader est la deuxième méthode appelée dans Render. SetShaderParameters est appelée avant pour s'assurer que les paramètres de shader sont correctement configurés.

Cette fonction commence par activer la disposition des données en entrée dans l'assembleur d'entrée. Cela permet au GPU de connaître le format des données dans le tampon des sommets. La deuxième étape consiste à définir les vertex et pixel shaders que nous allons utiliser pour le rendu du tampon des sommets. Une fois que les shaders sont fixés, nous effectuons le rendu du triangle en appelant la fonction DrawIndexed de DirectX 11 qui utilise le contexte de périphérique D3D. Le triangle vert sera affiché une fois la fonction appelée.

Sélectionnez
void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
    // Définit la disposition des données en entrée du vertex.
    deviceContext->IASetInputLayout(m_layout);

    // Définit les vertex et pixel shaders qui seront utilisés pour le rendu du triangle.
    deviceContext->VSSetShader(m_vertexShader, NULL, 0);
    deviceContext->PSSetShader(m_pixelShader, NULL, 0);

    // Effectue le rendu du triangle.
    deviceContext->DrawIndexed(indexCount, 0, 0);

    return;
}

XIV. Cameraclass.h

Nous avons étudié la façon de coder des shaders HLSL, la configuration des tampons d'indices et de sommets, et l'appel des shaders HLSL visant à dessiner ces tampons en utilisant la classe ColorShaderClass. Il ne nous manque qu'une chose : le point de vue duquel nous les affichons. Pour cela, nous aurons besoin d'une classe Camera qui renseignera DirectX 11 sur la façon dont nous voyons la scène. Cette classe gardera la trace de la position et de l'angle de vue de la caméra. Elle utilisera ces informations pour générer une matrice de vue qui sera transmise au shader HLSL pour le rendu.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : cameraclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _CAMERACLASS_H_
#define _CAMERACLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3dx10math.h>

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : CameraClass
////////////////////////////////////////////////////////////////////////////////
class CameraClass
{
public:
    CameraClass();
    CameraClass(const CameraClass&);
    ~CameraClass();

    void SetPosition(float, float, float);
    void SetRotation(float, float, float);

    D3DXVECTOR3 GetPosition();
    D3DXVECTOR3 GetRotation();

    void Render();
    void GetViewMatrix(D3DXMATRIX&);

private:
    float m_positionX, m_positionY, m_positionZ;
    float m_rotationX, m_rotationY, m_rotationZ;
    D3DXMATRIX m_viewMatrix;
};

#endif

L'en-tête de la classe CameraClass est assez simple avec seulement quatre méthodes. SetPosition et SetRotation seront utilisées pour définir la position et l'angle de la caméra. Render sera utilisée pour créer la matrice de vue à partir de la position et l'angle de la caméra. Enfin GetViewMatrix sera utilisée pour récupérer la matrice de vue de la caméra de sorte que les shaders puissent l'utiliser pour le rendu.

XV. Cameraclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : cameraclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "cameraclass.h"

Le constructeur de la classe va initialiser la position et l'angle de la caméra pour se situer à l'origine de la scène.

Sélectionnez
CameraClass::CameraClass()
{
    m_positionX = 0.0f;
    m_positionY = 0.0f;
    m_positionZ = 0.0f;

    m_rotationX = 0.0f;
    m_rotationY = 0.0f;
    m_rotationZ = 0.0f;
}

CameraClass::CameraClass(const CameraClass& other)
{
}

CameraClass::~CameraClass()
{
}

Les méthodes SetPosition et SetRotation sont utilisées pour régler la position et l'angle de la caméra.

Sélectionnez
void CameraClass::SetPosition(float x, float y, float z)
{
    m_positionX = x;
    m_positionY = y;
    m_positionZ = z;
    return;
}

void CameraClass::SetRotation(float x, float y, float z)
{
    m_rotationX = x;
    m_rotationY = y;
    m_rotationZ = z;
    return;
}

Les fonctions GetPosition et GetRotation retournent la position et l'angle de la caméra aux fonctions appelantes.

Sélectionnez
D3DXVECTOR3 CameraClass::GetPosition()
{
    return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}

D3DXVECTOR3 CameraClass::GetRotation()
{
    return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}

La méthode Render utilise la position et l'angle de la caméra pour construire et mettre à jour la matrice de vue. Nous définissons d'abord les variables up, position, rotation, et ainsi de suite. Puis, depuis l'origine de la scène, on fait tourner la caméra selon ses axes x, y et z. Ensuite, nous déplaçons la caméra à sa position dans l'espace 3D. Avec les bonnes valeurs dans les variables position, lookAt, et up, nous pouvons utiliser la fonction D3DXMatrixLookAtLH pour créer la matrice de vue représentant l'angle actuel de la caméra et son déplacement.

Sélectionnez
void CameraClass::Render()
{
    D3DXVECTOR3 up, position, lookAt;
    float yaw, pitch, roll;
    D3DXMATRIX rotationMatrix;

    // Définit un vecteur dirigé vers le haut.
    up.x = 0.0f;
    up.y = 1.0f;
    up.z = 0.0f;

    // Définit la position de la caméra dans le monde.
    position.x = m_positionX;
    position.y = m_positionY;
    position.z = m_positionZ;

    // Définit la direction de la caméra par défaut.
    lookAt.x = 0.0f;
    lookAt.y = 0.0f;
    lookAt.z = 1.0f;

    // Définit l'embardée (yaw, selon l'axe des Y), le tangage (pitch, axe des X) et le roulement (roll, axe des Z) en radians.
    pitch = m_rotationX * 0.0174532925f;
    yaw   = m_rotationY * 0.0174532925f;
    roll  = m_rotationZ * 0.0174532925f;

    // Crée la matrice de rotation à partir des valeurs de yaw, pitch et roll.
    D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll);

    // Transforme les vecteurs lookAt et up à l'aide de la matrice de rotation afin qu'ils soient correctement tournés au début.
    D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix);
    D3DXVec3TransformCoord(&up, &up, &rotationMatrix);

    // Déplace la caméra tournée à la position du spectateur.
    lookAt = position + lookAt;

    // Crée finalement la matrice de vue à partir des vecteurs mis à jour.
    D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up);

    return;
}

Suite à l'appel de la méthode Render mettant à jour la matrice de vue, nous pouvons fournir cette dernière aux fonctions appelantes par le biais de la méthode GetViewMatrix. La matrice de vue sera l'une des trois principales matrices utilisées dans le shader vertex HLSL.

Sélectionnez

void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
    viewMatrix = m_viewMatrix;
    return;
}

XVI. Graphicsclass.h

La classe GraphicsClass possède maintenant trois nouvelles classes : CameraClass, ModelClass et ColorShaderClass dont nous avons ici ajouté les en-têtes, ainsi que les attributs privés. Rappelez-vous que GraphicsClass est la classe principale qui est utilisée pour effectuer le rendu de la scène, ceci en invoquant toutes les instances de classe nécessaires au projet.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

/////////////////////////////
// INCLUSIONS DE MA CLASSE //
/////////////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "colorshaderclass.h"

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
    GraphicsClass();
    GraphicsClass(const GraphicsClass&);
    ~GraphicsClass();

    bool Initialize(int, int, HWND);
    void Shutdown();
    bool Frame();

private:
    bool Render();

private:
    D3DClass* m_D3D;
    CameraClass* m_Camera;
    ModelClass* m_Model;
    ColorShaderClass* m_ColorShader;
};

#endif

XVII. Graphicsclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

Le premier changement de la classe GraphicsClass est l'initialisation à 0 de la caméra, du modèle et du shader de couleur dans son constructeur.

Sélectionnez
GraphicsClass::GraphicsClass()
{
    m_D3D = 0;
    m_Camera = 0;
    m_Model = 0;
    m_ColorShader = 0;
}

La méthode Initialize a également été mise à jour pour créer et initialiser les trois nouveaux objets.

Sélectionnez
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
    bool result;

    // Crée l'objet Direct3D.
    m_D3D = new D3DClass;
    if(!m_D3D)
    {
        return false;
    }

    // Initialise l'objet Direct3D.
    result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
        return false;
    }

    // Crée l'objet Camera.
    m_Camera = new CameraClass;
    if(!m_Camera)
    {
        return false;
    }

    // Fixe la position initiale de la caméra.
    m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

    // Crée l'objet Model.
    m_Model = new ModelClass;
    if(!m_Model)
    {
        return false;
    }

    // Initialise l'objet Model.
    result = m_Model->Initialize(m_D3D->GetDevice());
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
        return false;
    }

    // Crée l'objet Shader de couleur.
    m_ColorShader = new ColorShaderClass;
    if(!m_ColorShader)
    {
        return false;
    }

    // Initialise l'objet Shader de couleur.
    result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd);
    if(!result)
    {
        MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
        return false;
    }

    return true;
}

Également la méthode Shutdown afin de les libérer.

Sélectionnez
void GraphicsClass::Shutdown()
{
    // Libère l'objet Shader de couleurs.
    if(m_ColorShader)
    {
        m_ColorShader->Shutdown();
        delete m_ColorShader;
        m_ColorShader = 0;
    }

    // Libère l'objet Model.
    if(m_Model)
    {
        m_Model->Shutdown();
        delete m_Model;
        m_Model = 0;
    }

    // Libère l'objet Caméra.
    if(m_Camera)
    {
        delete m_Camera;
        m_Camera = 0;
    }

    // Libère l'objet Direct3D.
    if(m_D3D)
    {
        m_D3D->Shutdown();
        delete m_D3D;
        m_D3D = 0;
    }

    return;
}

La méthode Frame est restée la même que lors du précédent tutoriel.

Sélectionnez
bool GraphicsClass::Frame()
{
    bool result;

    // Effectue le rendu de la scène.
    result = Render();
    if(!result)
    {
        return false;
    }

    return true;
}

Comme on peut s'y attendre, c'est la méthode Render qui possède le plus de changements. Elle débute toujours avec l'effacement de la scène, excepté que nous l'effaçons avec la couleur noire. Après cela, elle appelle la méthode Render de la caméra pour créer une matrice de vue basée sur sa position définie dans la méthode Initialize. Une fois que la matrice de vue est créée, nous en récupérons une copie. Nous recueillons également des copies des matrices monde et de projection depuis l'objet D3DClass. Nous appelons ensuite la méthode ModelClass::Render afin de passer la géométrie du modèle du triangle vert sur le pipeline graphique. Les sommets ainsi préparés, nous appelons le shader de couleur pour dessiner les sommets en utilisant les informations du modèle et les trois matrices pour positionner chaque sommet. Le triangle vert est maintenant établi dans le tampon arrière. Avec ceci, la scène est terminée, nous appelons EndScene pour l'afficher à l'écran.

Sélectionnez
bool GraphicsClass::Render()
{
    D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
    bool result;

    // Efface les tampons pour débuter la scène.
    m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

    // Génère la matrice de vue à partir de la position de la caméra.
    m_Camera->Render();

    // Récupère les matrices monde, de vue et de projection depuis les objets Caméra et D3D.
    m_Camera->GetViewMatrix(viewMatrix);
    m_D3D->GetWorldMatrix(worldMatrix);
    m_D3D->GetProjectionMatrix(projectionMatrix);

    // Place les tampons des sommets et d'indices du modèle dans le pipeline graphique pour les préparer à être rendus.
    m_Model->Render(m_D3D->GetDeviceContext());

    // Effectue le rendu du modèle en utilisant le shader de couleurs.
    result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
    if(!result)
    {
        return false;
    }

    // Affiche la scène rendue à l'écran.
    m_D3D->EndScene();

    return true;
}

XVIII. Résumé

Image non disponible

En résumé, vous devriez avoir appris les rudiments du fonctionnement des tampons d'indices et de sommets, ainsi que les bases des vertex et pixel shaders et comment les écrire en utilisant le HLSL. Enfin, vous devriez comprendre comment nous avons intégré ces nouveaux concepts dans notre structure pour afficher un triangle vert à l'écran. Je tiens également à mentionner que je me rends compte que le code est plutôt long pour juste dessiner un triangle alors qu'il aurait très bien pu tenir dans une unique fonction main().Je l'ai cependant fait de cette façon dans une structure appropriée afin que les tutoriels à venir ne nécessitent que très peu de changements pour faire des graphiques beaucoup plus complexes.

XIX. Exercices à faire

  1. Compilez et exécutez le tutoriel. Assurez-vous qu'il dessine un triangle vert à l'écran. Appuyez sur « Echap » pour quitter le programme une fois fait.
  2. Changez la couleur du triangle en rouge.
  3. Modifiez le triangle en carré.
  4. Déplacez la caméra de dix unités en arrière.
  5. Modifiez le pixel shader pour qu'il ne donne que la moitié de la luminosité (énorme indice : multiplier quelque chose dans ColorPixelShader par 0.5f).

XX. Code source

  • Projet Visual Studio 2010 : dx11tut04.zip
  • Source seule : dx11src04.zip
  • Exécutable seul : dx11exe04.zip

XXI. Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Copyright © 2000-2015 - www.developpez.com


ASM Mode Reel

DirectX 11 - Tutoriel 5 : Application de textures

Table des matières

  • Navigation
  • II. Introduction
  • III. Texture.vs
  • IV. Texture.ps
  • V. Textureclass.h
  • VI. Textureclass.cpp
  • VII. Modelclass.h
  • VIII. Modelclass.cpp
  • IX. Textureshaderclass.h
  • X. Textureshaderclass.cpp
  • XI. Graphicsclass.h
  • XII. Graphicsclass.cpp
  • XIII. Résumé
  • XIV. Exercices à faire
  • XV. Code source
  • XVI. Remerciements
  • Navigation

Ce tutoriel va vous expliquer comment appliquer une texture dans DirectX 11. Cela nous permet d'ajouter du réalisme à nos scènes en appliquant des photos et autres images sur les faces des polygones.

II. Introduction

Dans ce tutoriel, nous prendrons pour exemple cette image :

Image non disponible

Que nous appliquerons au polygone du tutoriel précédent afin d'obtenir ceci :

Image non disponible

Nous utiliserons le format DDS pour nos textures. C'est le format Direct Draw Surface que DirectX utilise. L'outil utilisé pour créer des fichiers .dds est livré avec le SDK de DirectX. L'outil se nomme DirectX Texture Tool, il est situé dans DirectX Utilities. Vous pouvez créer des textures de toutes tailles et tous formats, puis couper et coller votre image vers un autre format de texture et l'enregistrer comme un fichier .dds. Il est très simple à utiliser.

Avant de rentrer dans le code, nous devrions voir comment l'application de texture fonctionne. Pour lier des pixels de l'image dds au polygone, nous utilisons ce qu'on appelle le « système de coordonnées Texel ». Ce système convertit la valeur entière du pixel vers une valeur flottante entre 0.0f et 1.0f. Par exemple, pour une texture de 256 pixels de largeur, le premier pixel est à 0.0f, le 256e pixel à 1.0f, et le 128e pixel (au centre) à 0.5f.

Dans le système de coordonnées Texel, la valeur selon la largeur est nommée « U » et la valeur selon la hauteur est nommée « V ». La largeur va de 0.0 (à gauche) à 1.0 (à droite). La hauteur va de 0.0 (en haut) à 1.0 (en bas). Par exemple, le coin supérieur gauche sera noté U 0.0, V 0.0 et le coin inférieur droit sera noté U 1.0, V 1.0. Le schéma ci-dessous illustre ce système :

Image non disponible

Maintenant que nous avons les bases de l'application de textures sur les polygones, nous pouvons regarder notre cadre de travail actualisé pour ce tutoriel :

Image non disponible

Les modifications apportées depuis le tutoriel précédent concernent la nouvelle classe TextureClass au sein de la classe ModelClass, et TextureShaderClass qui remplace ColorShaderClass. Nous commencerons par regarder le code des nouveaux shaders de texture HLSL.

III. Texture.vs

Les vertex shaders sont similaires aux précédents, exceptées les parties intégrant l'application de texture.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : texture.vs
////////////////////////////////////////////////////////////////////////////////

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

Dans notre type, nous utilisons des coordonnées de texture à la place de la couleur. Puisque les coordonnées de texture nécessitent deux flottants U et V, nous utilisons le type float2. Pour les vertex et pixel shaders, la sémantique utilisée pour les coordonnées de texture est TEXCOORD0. Vous pouvez mettre n'importe quel nombre à la place de ce zéro final afin d'indiquer avec quel ensemble de coordonnées vous travaillez, étant donné que vous êtes autorisé à travailler avec plusieurs coordonnées de texture.

Sélectionnez
//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};

////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType TextureVertexShader(VertexInputType input)
{
    PixelInputType output;

    // Change la quatrième composante du vecteur pour avoir des calculs matriciels corrects.
    input.position.w = 1.0f;

    // Calcule la position du sommet selon les matrices monde, de vue et de projection.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

La seule différence dans le vertex shader de texture par rapport au vertex shader de couleur du tutoriel précédent est que, au lieu de faire une copie de la couleur du sommet en entrée, nous prenons les coordonnées de la texture et les transmettons au pixel shader.

Sélectionnez
    // Stocke les coordonnées de la texture pour le pixel shader.
    output.tex = input.tex;

    return output;
}

IV. Texture.ps

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : texture.ps
////////////////////////////////////////////////////////////////////////////////

Le pixel shader de texture a deux variables globales. La première Texture2D shaderTexture est la ressource de texture qui sera utilisée pour le rendu de la texture sur le modèle. La deuxième SamplerState SampleType, l'état de l'échantillonneur, nous permet de modifier la façon dont les pixels sont écrits dans la face du polygone lorsqu'elle est réduite. Par exemple, si le polygone est vraiment très loin et ne représente que 8 pixels sur l'écran, nous utilisons l'état de l'échantillonneur pour déterminer quels pixels ou quelle combinaison de pixels de la texture originale seront effectivement dessinés. La texture originale peut faire 256x256 pixels, donc décider quels pixels seront dessinés est vraiment important afin de veiller à ce que la texture ressemble toujours à quelque chose, même sur de très petites faces. Nous définirons aussi l'état de l'échantillonneur dans la classe TextureShaderClass et l'attacherons au pointeur de ressources afin que ce pixel shader puisse l'utiliser pour déterminer quels pixels seront dessinés.

Sélectionnez
////////////////////////
// VARIABLES GLOBALES //
////////////////////////
Texture2D shaderTexture;
SamplerState SampleType;

Le type PixelInputType du pixel shader de texture est également modifié pour mettre les coordonnées de texture à la place des valeurs de couleur.

Sélectionnez
//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};

Le pixel shader a été modifié de sorte qu'il utilise dorénavant le code de la fonction d'échantillonnage HLSL. Cette fonction utilise l'état de l'échantillonneur défini ci-dessus et les coordonnées de texture du pixel afin de déterminer et retourner la valeur du pixel correspondant aux coordonnées UV sur la face du polygone.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 TexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;

    // Évalue la couleur du pixel à partir de la texture en utilisant l'échantillonneur aux coordonnées désignées.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

    return textureColor;
}

V. Textureclass.h

La classe TextureClass encapsule le chargement, le déchargement, et l'accès à une ressource de texture unique. Un objet de cette classe doit être instancié chaque fois que nous avons besoin d'une texture.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURECLASS_H_
#define _TEXTURECLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx11tex.h>

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : TextureClass
////////////////////////////////////////////////////////////////////////////////
class TextureClass
{
public:
        TextureClass();
        TextureClass(const TextureClass&);
        ~TextureClass();

Les deux premières méthodes chargeront une texture à partir du nom d'un fichier donné et la déchargeront quand elle ne sera plus nécessaire.

Sélectionnez
        bool Initialize(ID3D11Device*, WCHAR*);
        void Shutdown();

La méthode GetTexture renvoie un pointeur vers la texture de sorte qu'elle puisse être utilisée pour le rendu par les shaders.

Sélectionnez
        ID3D11ShaderResourceView* GetTexture();

private:
        // C'est la ressource de texture privée.
        ID3D11ShaderResourceView* m_texture;
};

#endif

VI. Textureclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "textureclass.h"

Le constructeur de la classe initialise à zéro le pointeur de la texture du shader.

Sélectionnez
TextureClass::TextureClass()
{
        m_texture = 0;
}

TextureClass::TextureClass(const TextureClass& other)
{
}

TextureClass::~TextureClass()
{
}

La méthode Initialize prend en paramètre le dispositif Direct3D et le nom du fichier de la texture. Elle charge le fichier dans la variable ressource du shader nommée m_texture. La texture peut maintenant être utilisée pour effectuer le rendu.

Sélectionnez
bool TextureClass::Initialize(ID3D11Device* device, WCHAR* filename)
{
        HRESULT result;

        // Charge la texture.
        result = D3DX11CreateShaderResourceViewFromFile(device, filename, NULL, NULL, &m_texture, NULL);
        if(FAILED(result))
        {
                return false;
        }

        return true;
}

La méthode Shutdown libère la ressource de la texture si elle a été chargée et met le pointeur à la valeur nulle.

Sélectionnez
void TextureClass::Shutdown()
{
        // Libère la ressource de la texture.
        if(m_texture)
        {
                m_texture->Release();
                m_texture = 0;
        }

        return;
}

La méthode GetTexture est appelée par d'autres objets qui ont besoin d'accéder à la ressource de la texture du shader afin qu'ils puissent l'utiliser pour le rendu.

Sélectionnez
ID3D11ShaderResourceView* TextureClass::GetTexture()
{
        return m_texture;
}

VII. Modelclass.h

La classe ModelClass a changé de façon à permettre l'application de texture.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>

L'en-tête de la classe TextureClass est maintenant inclus.

Sélectionnez
/////////////////////////////
// INCLUSIONS DE MA CLASSE //
/////////////////////////////
#include "textureclass.h" // NOUVEAU

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:

Le type VertexType remplace la composante couleur (verte, utilisée dans le tutoriel précédent) par les coordonnées de texture.

Sélectionnez
        struct VertexType // MODIFIÉ
        {
                D3DXVECTOR3 position;
                D3DXVECTOR2 texture;
        };

public:
        ModelClass();
        ModelClass(const ModelClass&);
        ~ModelClass();

        bool Initialize(ID3D11Device*, WCHAR*);
        void Shutdown();
        void Render(ID3D11DeviceContext*);

        int GetIndexCount();

La classe ModelClass a également une méthode GetTexture afin qu'elle puisse passer sa propre ressource de texture de shaders qui dessinera le modèle.

Sélectionnez
        ID3D11ShaderResourceView* GetTexture();

private:
        bool InitializeBuffers(ID3D11Device*);
        void ShutdownBuffers();
        void RenderBuffers(ID3D11DeviceContext*);

La classe ModelClass possède deux méthodes privées LoadTexture et ReleaseTexture pour le chargement et la libération de la texture qui sera utilisée pour le rendu de ce modèle.

Sélectionnez
        bool LoadTexture(ID3D11Device*, WCHAR*); // NOUVEAU
        void ReleaseTexture(); // NOUVEAU

private:
        ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
        int m_vertexCount, m_indexCount;

La variable m_Texture est utilisée pour le chargement, la libération, et l'accès à la texture du modèle.

Sélectionnez
        TextureClass* m_Texture; // NOUVEAU
};

#endif

VIII. Modelclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"

ModelClass::ModelClass()
{
        m_vertexBuffer = 0;
        m_indexBuffer = 0;

Le constructeur de la classe initialise maintenant à la valeur nulle le nouvel objet texture.

Sélectionnez
        m_Texture = 0; // NOUVEAU
}

ModelClass::ModelClass(const ModelClass& other)
{
}

ModelClass::~ModelClass()
{
}

La méthode Initialize prend maintenant en entrée le nom du fichier .dds de la texture que le modèle utilisera

Sélectionnez
bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename) //  MODIFIÉ
{
        bool result;

        // Initialise les tampons de sommets et d'indices qui contiennent la géométrie du triangle.
        result = InitializeBuffers(device);
        if(!result)
        {
                return false;
        }

… et appelle maintenant une nouvelle fonction privée qui va charger la texture.

Sélectionnez
        // NOUVEAU - charge la texture pour ce modèle.
        result = LoadTexture(device, textureFilename);
        if(!result)
        {
                return false;
        }

        return true;
}

void ModelClass::Shutdown()
{

La fonction Shutdown appelle maintenant la nouvelle fonction privée pour libérer l'objet texture chargé lors de l'initialisation.

Sélectionnez
        // // NOUVEAU - libère la texture du modèle.
        ReleaseTexture();

        // Libère les tampons de sommets et d'indices.
        ShutdownBuffers();

        return;
}

void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
        // Place les tampons de sommets et d'indices sur le pipeline graphique pour les préparer à être dessinés.
        RenderBuffers(deviceContext);

        return;
}

int ModelClass::GetIndexCount()
{
        return m_indexCount;
}

La méthode GetTexture retourne la texture du modèle. Le shader de texture aura besoin d'y accéder pour effectuer le rendu du modèle.

Sélectionnez
ID3D11ShaderResourceView* ModelClass::GetTexture() // NOUVEAU
{
        return m_Texture->GetTexture();
}

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
        VertexType* vertices;
        unsigned long* indices;
        D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
        D3D11_SUBRESOURCE_DATA vertexData, indexData;
        HRESULT result;

        // Fixe la taille du tableau de sommets.
        m_vertexCount = 3;

        // Fixe la taille du tableau d'indices.
        m_indexCount = 3;

        // Crée le tableau de sommets.
        vertices = new VertexType[m_vertexCount];
        if(!vertices)
        {
                return false;
        }

        // Crée le tableau d'indices.
        indices = new unsigned long[m_indexCount];
        if(!indices)
        {
                return false;
        }

Le tableau des sommets a maintenant une composante de texture à la place de la composante de couleur. Le vecteur texture a toujours U en premier et V en second. Par exemple, la première coordonnée de texture est celle en bas à gauche du triangle correspondant à U 0.0, V 1.0. Référez-vous à pour savoir quelles coordonnées utiliser. Notez que vous pouvez modifier ces coordonnées pour lier n'importe quelle partie de la texture à n'importe quelle partie de la face du polygone. Dans ce tutoriel, je fais juste une correspondance directe pour des raisons de simplicité.

Sélectionnez
        // Charge les données dans le tableau de sommets.
        vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // En bas à gauche.
        vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f); //  MODIFIÉ

        vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // En haut au milieu.
        vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f); //  MODIFIÉ

        vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // En bas à droite.
        vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f); //  MODIFIÉ

        // Charge les données dans le tableau d'indices.
        indices[0] = 0;  // En bas à gauche.
        indices[1] = 1;  // En haut au milieu.
        indices[2] = 2;  // En bas à droite.

        // Met en place la description du tampon de sommets.
        vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;
        vertexBufferDesc.MiscFlags = 0;
        vertexBufferDesc.StructureByteStride = 0;

        // Donne à la structure de sous-ressources un pointeur vers le tableau des sommets.
        vertexData.pSysMem = vertices;
        vertexData.SysMemPitch = 0;
        vertexData.SysMemSlicePitch = 0;

        // Crée le tampon des sommets.
        result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Met en place la description du tampon statique des indices.
        indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
        indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;
        indexBufferDesc.StructureByteStride = 0;

        // Donne à la structure de sous-ressources un pointeur vers le tableau des indices.
        indexData.pSysMem = indices;

        // Crée le tampon des indices.
        result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Libère les tableaux, maintenant que les tampons ont été créés et remplis.
        delete [] vertices;
        vertices = 0;

        delete [] indices;
        indices = 0;

        return true;
}

void ModelClass::ShutdownBuffers()
{
        // Libère le tampon d'indices.
        if(m_indexBuffer)
        {
                m_indexBuffer->Release();
                m_indexBuffer = 0;
        }

        // Libère le tampon de sommets.
        if(m_vertexBuffer)
        {
                m_vertexBuffer->Release();
                m_vertexBuffer = 0;
        }

        return;
}

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
        unsigned int stride;
        unsigned int offset;

        // Règle le pas et l'offset du tampon.
        stride = sizeof(VertexType);
        offset = 0;

        // Active le tampon des sommets dans l'assembleur d'entrées pour effectuer son rendu.
        deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

        // Active le tampon des indices dans l'assembleur d'entrées pour effectuer son rendu.
        deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

        // Définit le type de primitive qui doit être rendue depuis ce tampon de sommets, ici un triangle.
        deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        return;
}

LoadTexture est une nouvelle méthode privée qui va créer l'objet texture et l'initialiser avec le nom du fichier d'entrée fourni. Cette fonction est appelée lors de l'initialisation.

Sélectionnez
bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename) // NOUVEAU
{
        bool result;

        // Crée l'objet texture.
        m_Texture = new TextureClass;
        if(!m_Texture)
        {
                return false;
        }

        // Initialise l'objet texture.
        result = m_Texture->Initialize(device, filename);
        if(!result)
        {
                return false;
        }

        return true;
}

La méthode ReleaseTexture libérera l'objet texture créé et chargé dans la méthode LoadTexture.

Sélectionnez
void ModelClass::ReleaseTexture() // NOUVEAU
{
        // Libère l'objet texture.
        if(m_Texture)
        {
                m_Texture->Shutdown();
                delete m_Texture;
                m_Texture = 0;
        }

        return;
}

IX. Textureshaderclass.h

La classe TextureShaderClass est juste une version mise à jour de ColorShaderClass du tutoriel précédent. Cette classe sera utilisée pour dessiner les modèles 3D en utilisant les vertex et pixel shaders.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTURESHADERCLASS_H_
#define _TEXTURESHADERCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : TextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class TextureShaderClass
{
private:
        struct MatrixBufferType
        {
                D3DXMATRIX world;
                D3DXMATRIX view;
                D3DXMATRIX projection;
        };

public:
        TextureShaderClass();
        TextureShaderClass(const TextureShaderClass&);
        ~TextureShaderClass();

        bool Initialize(ID3D11Device*, HWND);
        void Shutdown();
        bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);

private:
        bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
        void ShutdownShader();
        void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

        bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*);
        void RenderShader(ID3D11DeviceContext*, int);

private:
        ID3D11VertexShader* m_vertexShader;
        ID3D11PixelShader* m_pixelShader;
        ID3D11InputLayout* m_layout;
        ID3D11Buffer* m_matrixBuffer;

Une nouvelle variable privée est introduite : le pointeur vers l'état de l'échantillonneur. Ce pointeur sera utilisé pour l'interface avec le shader de texture.

Sélectionnez
        ID3D11SamplerState* m_sampleState;
};

#endif

X. Textureshaderclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : textureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "textureshaderclass.h"

TextureShaderClass::TextureShaderClass()
{
        m_vertexShader = 0;
        m_pixelShader = 0;
        m_layout = 0;
        m_matrixBuffer = 0;

Le nouveau pointeur sur l'état de l'échantillonneur est mis à la valeur nulle dans le constructeur de la classe.

Sélectionnez
        m_sampleState = 0;
}

TextureShaderClass::TextureShaderClass(const TextureShaderClass& other)
{
}

TextureShaderClass::~TextureShaderClass()
{
}

bool TextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
        bool result;

Les nouveaux fichiers HLSL texture.vs et texture.ps sont chargés pour ce shader.

Sélectionnez
        // Initialise les vertex et pixel shaders.
        result = InitializeShader(device, hwnd, L"../Engine/texture.vs", L"../Engine/texture.ps");
        if(!result)
        {
                return false;
        }

        return true;
}

La méthode Shutdown appelle la fonction de libération des variables shaders.

Sélectionnez
void TextureShaderClass::Shutdown()
{
        // Ferme les vertex et pixel shaders ainsi que les objets associés.
        ShutdownShader();

        return;
}

La méthode Render prend désormais un nouveau paramètre nommé texture représentant le pointeur vers la ressource de texture. Celui-ci est envoyé dans la méthode SetShaderParameters afin qu'elle soit définie dans le shader et utilisée pour le rendu.

Sélectionnez
bool TextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                                D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)
{
        bool result;

        // Définit les paramètres du shader qui seront utilisés pour le rendu.
        result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture);
        if(!result)
        {
                return false;
        }

        // Effectue le rendu des tampons avec le shader.
        RenderShader(deviceContext, indexCount);

        return true;
}

La méthode InitializeShader met en place le shader de texture.

Sélectionnez
bool TextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
        HRESULT result;
        ID3D10Blob* errorMessage;
        ID3D10Blob* vertexShaderBuffer;
        ID3D10Blob* pixelShaderBuffer;
        D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
        unsigned int numElements;
        D3D11_BUFFER_DESC matrixBufferDesc;

Nous avons une nouvelle variable qui contient la description de l'échantillonneur de la texture et qui sera configurée dans cette méthode.

Sélectionnez
        D3D11_SAMPLER_DESC samplerDesc;

        // Initialise à zéro les pointeurs que la méthode va utiliser.
        errorMessage = 0;
        vertexShaderBuffer = 0;
        pixelShaderBuffer = 0;

Charge les nouveaux vertex et pixel shaders de texture.

Sélectionnez
        // Compile le code du vertex shader.
        result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "TextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
                                       &vertexShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
        {
                // Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
                if(errorMessage)
                {
                        OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
                }
                // Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
                else
                {
                        MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
                }

                return false;
        }

        // Compile le code du pixel shader.
        result = D3DX11CompileFromFile(psFilename, NULL, NULL, "TexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
                                       &pixelShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
        {
                // Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
                if(errorMessage)
                {
                        OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
                }
                // Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
                else
                {
                        MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
                }

                return false;
        }

        // Crée le vertex shader à partir du tampon.
        result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
        if(FAILED(result))
        {
                return false;
        }

        // Crée le pixel shader à partir du tampon.
        result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
        if(FAILED(result))
        {
                return false;
        }

La disposition d'entrée a changé, nous avons maintenant un élément de texture à la place de la couleur. Le premier élément de la position reste inchangé, mais les champs SemanticName et Format du second élément ont été modifiés en TexCoord et DXGI_FORMAT_R32G32_FLOAT. Ces deux modifications sont alors en accord avec la disposition de notre nouveau VertexType à la fois dans la définition de la classe ModelClass et dans les alias des fichiers de shader.

Sélectionnez
        // Crée la description de la disposition d'entrée du vertex.
        // Cette disposition doit être la même que celle de la structure VextexType du shader.
        polygonLayout[0].SemanticName = "POSITION";
        polygonLayout[0].SemanticIndex = 0;
        polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
        polygonLayout[0].InputSlot = 0;
        polygonLayout[0].AlignedByteOffset = 0;
        polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[0].InstanceDataStepRate = 0;

        polygonLayout[1].SemanticName = "TEXCOORD";
        polygonLayout[1].SemanticIndex = 0;
        polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
        polygonLayout[1].InputSlot = 0;
        polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
        polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[1].InstanceDataStepRate = 0;

        // Obtient le nombre d'éléments de la disposition des données.
        numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

        // Crée la disposition d'entrée du vertex.
        result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(),
                                           &m_layout);
        if(FAILED(result))
        {
                return false;
        }

        // Libère les vertex et pixel shaders, car nous n'en avons plus besoin.
        vertexShaderBuffer->Release();
        vertexShaderBuffer = 0;

        pixelShaderBuffer->Release();
        pixelShaderBuffer = 0;

        // Met en place la description du tampon constant de matrice dynamique situé dans le vertex shader.
        matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
        matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
        matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        matrixBufferDesc.MiscFlags = 0;
        matrixBufferDesc.StructureByteStride = 0;

        // Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
        result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
        if(FAILED(result))
        {
                return false;
        }

La description de l'état de l'échantillonneur de la texture est configurée ici et peut ensuite être transmise au pixel shader. L'élément le plus important est le champ Filtre qui détermine la méthode utilisée pour garder et combiner les pixels servant à créer l'apparence finale de la texture sur la face du polygone. Dans cet exemple, j'utilise D3D11_FILTER_MIN_MAG_MIP_LINEAR qui est plus coûteux en termes de traitement, mais donne le meilleur résultat visuel. Il demande à l'échantillonneur d'effectuer une interpolation linéaire pour le rétrécissement, le grossissement et l'échantillonnage de MIP maps.

Les champs AddressU et AddressV sont mis à Wrap, ce qui garantit que les coordonnées restent entre 0.0f et 1.0f. Celles qui se trouvent en dehors sont ramenées dans cet intervalle. Tous les autres paramètres sont définis par défaut.

Sélectionnez
        // Crée une description de l'état de l'échantillonneur de la texture.
        samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = 1;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
        samplerDesc.BorderColor[0] = 0;
        samplerDesc.BorderColor[1] = 0;
        samplerDesc.BorderColor[2] = 0;
        samplerDesc.BorderColor[3] = 0;
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        // Crée l'état de l'échantillonneur de la texture.
        result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
        if(FAILED(result))
        {
                return false;
        }

        return true;
}

La méthode ShutdownShader libère toutes les variables utilisées dans la classe.

Sélectionnez
void TextureShaderClass::ShutdownShader()
{

Elle libère maintenant le nouvel état de l'échantillonneur créé lors de l'initialisation.

Sélectionnez
        // Libère l'état de l'échantillonneur.
        if(m_sampleState)
        {
                m_sampleState->Release();
                m_sampleState = 0;
        }

        // Libère le tampon de matrice constant.
        if(m_matrixBuffer)
        {
                m_matrixBuffer->Release();
                m_matrixBuffer = 0;
        }

        // Libère la disposition.
        if(m_layout)
        {
                m_layout->Release();
                m_layout = 0;
        }

        // Libère le pixel shader.
        if(m_pixelShader)
        {
                m_pixelShader->Release();
                m_pixelShader = 0;
        }

        // Libère le vertex shader.
        if(m_vertexShader)
        {
                m_vertexShader->Release();
                m_vertexShader = 0;
        }

        return;
}

La méthode OutputShaderErrorMessage écrit les erreurs dans un fichier texte si le shader HLSL n'a pas pu être chargé.

Sélectionnez
void TextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
        char* compileErrors;
        unsigned long bufferSize, i;
        ofstream fout;

        // Récupère le pointeur vers le message d'erreur du tampon.
        compileErrors = (char*)(errorMessage->GetBufferPointer());

        // Obtient la longueur du message.
        bufferSize = errorMessage->GetBufferSize();

        // Ouvre un fichier pour y écrire le message d'erreur.
        fout.open("shader-error.txt");

        // Écrit le message d'erreur.
        for(i=0; i<bufferSize; i++)
        {
                fout << compileErrors[i];
        }

        // Ferme le fichier.
        fout.close();

        // Libère le message d'erreur.
        errorMessage->Release();
        errorMessage = 0;

        // Affiche un message à l'écran pour avertir l'utilisateur qu'il doit vérifier le contenu du fichier pour y trouver les erreurs de compilation.
        MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

        return;
}

La méthode SetShaderParameters prend maintenant un pointeur vers une ressource de texture pour le transmettre au shader. Notez que la texture doit être fixée avant le rendu du tampon.

Sélectionnez
bool TextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                                             D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture)
{
        HRESULT result;
        D3D11_MAPPED_SUBRESOURCE mappedResource;
        MatrixBufferType* dataPtr;
        unsigned int bufferNumber;

        // Transpose les matrices afin de les préparer pour le shader.
        D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
        D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
        D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

        // Pose un verrou sur le tampon constant afin que l'on puisse écrire dedans.
        result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
        if(FAILED(result))
        {
                return false;
        }

        // Récupère le pointeur des données dans le tampon constant.
        dataPtr = (MatrixBufferType*)mappedResource.pData;

        // Copie la matrice dans le tampon constant.
        dataPtr->world = worldMatrix;
        dataPtr->view = viewMatrix;
        dataPtr->projection = projectionMatrix;

        // Déverrouille le tampon constant.
        deviceContext->Unmap(m_matrixBuffer, 0);

        // Définit la position du tampon constant dans le vertex shader.
        bufferNumber = 0;

        // Fixe le tampon constant contenant les données à jour dans le vertex shader.
        deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

La méthode SetShaderParameters a été modifiée pour intégrer la texture dans le pixel shader.

Sélectionnez
        // Définit la texture dans le pixel shader.
        deviceContext->PSSetShaderResources(0, 1, &texture);

        return true;
}

La méthode RenderShader appelle le shader pour effectuer le rendu des polygones.

Sélectionnez
void TextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
        // Définit la disposition des données en entrée du vertex.
        deviceContext->IASetInputLayout(m_layout);

        // Définit les vertex et pixel shaders qui seront utilisés pour le rendu du triangle.
        deviceContext->VSSetShader(m_vertexShader, NULL, 0);
        deviceContext->PSSetShader(m_pixelShader, NULL, 0);

La méthode RenderShader intègre maintenant l'état de l'échantillonneur dans le pixel shader avant le rendu.

Sélectionnez
        // Définit l'état de l'échantillonneur dans le pixel shader.
        deviceContext->PSSetSamplers(0, 1, &m_sampleState);

        // Effectue le rendu du triangle.
        deviceContext->DrawIndexed(indexCount, 0, 0);

        return;
}

XI. Graphicsclass.h

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

/////////////////////////////
// INCLUSIONS DE LA CLASSE //
/////////////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"

La classe GraphicsClass comprend maintenant le fichier d'en-tête de la classe TextureShaderClass tandis que celui de la classe ColorShaderClass a été supprimé.

Sélectionnez
#include "textureshaderclass.h" // NOUVEAU

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
        GraphicsClass();
        GraphicsClass(const GraphicsClass&);
        ~GraphicsClass();

        bool Initialize(int, int, HWND);
        void Shutdown();
        bool Frame();

private:
        bool Render();

private:
        D3DClass* m_D3D;
        CameraClass* m_Camera;
        ModelClass* m_Model;

Nous avons ajouté un nouvel objet TextureShaderClass privé.

A new TextureShaderClass private object has been added.

Sélectionnez
        TextureShaderClass* m_TextureShader; // NOUVEAU
};

#endif

XII. Graphicsclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

La variable m_TextureShader est définie à la valeur nulle dans le constructeur.

Sélectionnez
GraphicsClass::GraphicsClass()
{
        m_D3D = 0;
        m_Camera = 0;
        m_Model = 0;
        m_TextureShader = 0; // NOUVEAU
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
        bool result;

        // Crée l'objet Direct3D.
        m_D3D = new D3DClass;
        if(!m_D3D)
        {
                return false;
        }

        // Initialise l'objet Direct3D.
        result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
                return false;
        }

        // Crée l'objet Camera.
        m_Camera = new CameraClass;
        if(!m_Camera)
        {
                return false;
        }

        // Crée l'objet Model.
        m_Model = new ModelClass;
        if(!m_Model)
        {
                return false;
        }

La méthode ModelClass::initialize prend désormais le nom de la texture utilisée pour le rendu du modèle.

Sélectionnez
        // Initialise l'objet Model.
        result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds"); // MODIFIÉ
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
                return false;
        }

Le nouvel objet TextureShaderClass est créé et initialisé.

Sélectionnez
        // NOUVEAU - crée l'objet shader de texture.
        m_TextureShader = new TextureShaderClass;
        if(!m_TextureShader)
        {
                return false;
        }

        // NOUVEAU - initialise l'objet shader de texture.
        result = m_TextureShader->Initialize(m_D3D->GetDevice(), hwnd);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the texture shader object.", L"Error", MB_OK);
                return false;
        }

        return true;
}

void GraphicsClass::Shutdown()
{

L'objet TextureShaderClass est également libéré dans la méthode Shutdown.

Sélectionnez
        // NOUVEAU - libère l'objet shader de texture.
        if(m_TextureShader)
        {
                m_TextureShader->Shutdown();
                delete m_TextureShader;
                m_TextureShader = 0;
        }

        // Libère l'objet Model.
        if(m_Model)
        {
                m_Model->Shutdown();
                delete m_Model;
                m_Model = 0;
        }

        // Libère l'objet Caméra.
        if(m_Camera)
        {
                delete m_Camera;
                m_Camera = 0;
        }

        // Libère l'objet Direct3D.
        if(m_D3D)
        {
                m_D3D->Shutdown();
                delete m_D3D;
                m_D3D = 0;
        }

        return;
}

bool GraphicsClass::Frame()
{
        bool result;

        // Effectue le rendu de la scène.
        result = Render();
        if(!result)
        {
                return false;
        }

        return true;
}

bool GraphicsClass::Render()
{
        D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
        bool result;

        // Efface les tampons pour débuter la scène.
        m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

        // Génère la matrice de vue à partir de la position de la caméra.
        m_Camera->Render();

        // Récupère les matrices monde, de vue et de projection depuis les objets Caméra et D3D.
        m_Camera->GetViewMatrix(viewMatrix);
        m_D3D->GetProjectionMatrix(projectionMatrix);
        m_D3D->GetWorldMatrix(worldMatrix);

        // Place les tampons des sommets et d'indices du modèle dans le pipeline graphique pour les préparer à être rendus.
        m_Model->Render(m_D3D->GetDevice());

Le shader de texture est maintenant appelé à la place du shader de couleur pour effectuer le rendu du modèle. Notez qu'il prend aussi le pointeur de la ressource de texture du modèle de sorte que le shader texture y ait accès.

Sélectionnez
        // NOUVEAU - effectue le rendu du modèle avec le shader de texture.
        result = m_TextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
                                         m_Model->GetTexture());
        if(!result)
        {
                return false;
        }

        // Affiche la scène rendue à l'écran.
        m_D3D->EndScene();

        return true;
}

XIII. Résumé▲

Vous devriez maintenant savoir charger une texture, la lier à un polygone, et effectuer son rendu avec un shader.

Image non disponible

XIV. Exercices à faire

  1. Recompilez le code et assurez-vous que la texture liée au triangle apparaisse à l'écran. Ceci fait, pressez la touche « Echap » pour quitter le programme ;
  2. Créez votre propre fichier .dds et placez-le dans le même dossier que le fichier seafloor.dds. Dans la méthode GraphicsClass::Initialize, changez le nom du modèle pour utiliser votre texture, puis recompilez et exécutez de nouveau votre programme ;
  3. Modifiez le code afin de créer deux triangles formant un carré. Liez la texture entière à ce carré de sorte qu'elle s'affiche correctement ;
  4. Faites varier la distance avec la caméra afin d'observer l'effet du filtre MIN_MAG_MIP_LINEAR ;
  5. Essayez d'autres filtres et bougez la caméra afin de voir les différents résultats.

XV. Code source

  • Projet Visual Studio 2010 : dx11tut05.zip
  • Source seule : dx11src05.zip
  • Exécutable seul : dx11exe05.zip

XVI. Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
Developpez.com

Copyright © 2000-2015 - www.developpez.com


ASM Mode Reel

DirectX 11 - Tutoriel 6 : Lumière diffuse

Table des matières

  • Navigation
  • II. Introduction
  • III. Framework
  • IV. Light.vs
  • V. Light.ps
  • VI. Lightshaderclass.h
  • VII. Lightshaderclass.cpp
  • VIII. Modelclass.h
  • IX. Modelclass.cpp
  • X. Lightclass.h
  • XI. Lightclass.cpp
  • XII. Graphicsclass.h
  • XIII. Graphicsclass.cpp
  • XIV. Résumé
  • XV. Exercices à faire
  • XVI. Source Code
  • XVII. Remerciements
  • Navigation

Dans ce tutoriel, je vais expliquer comment éclairer des objets 3D en utilisant un éclairage diffus et DirectX 11. Nous partirons du code du tutoriel précédent et le modifierons.

II. Introduction

Le type d'éclairage diffus que nous allons réaliser est appelé éclairage directionnel : il est comparable au rayonnement du Soleil sur la Terre. Il s'agit d'une source de lumière basée sur la direction, qui se situe à une grande distance. Elle émet une certaine quantité mesurable de lumière sur chaque objet. Toutefois, contrairement à l'éclairage ambiant (un autre modèle d'éclairage que nous verrons bientôt), elle n'éclairera pas les surfaces qu'elle ne touche pas directement.

J'ai choisi un éclairage directionnel pour débuter, car il est très facile à déboguer visuellement. En outre, puisqu'il ne nécessite que la direction, la formule est plus simple comparée aux autres types d'éclairage diffus tels que les spots ou lumières ponctuelles.

La mise en œuvre de l'éclairage diffus dans DirectX 11 s'effectue avec les vertex et pixel shaders. Il ne nécessite que la direction de la lumière et le vecteur normal des polygones que nous souhaitons éclairer. La direction est un vecteur unique que vous définissez, et vous pouvez obtenir la normale de chaque polygone en utilisant les trois sommets qui le composent. Dans ce tutoriel, nous incorporerons également la couleur de la lumière diffuse dans l'équation de l'éclairage.

III. Framework

Pour ce tutoriel, nous allons créer une nouvelle classe appelée LightClass qui représentera les sources de lumière dans les scènes. LightClass ne servira ici qu'à stocker la direction et la couleur de la lumière. Nous allons également supprimer la classe TextureShaderClass pour la remplacer par LightShaderClass qui gérera le rendu de la lumière sur le modèle. L'ajout de ces nouvelles classes au cadre de travail donnera ceci :

Image non disponible

Nous commencerons par regarder le code du shader HLSL de lumière. Vous remarquerez que c'est juste une version mise à jour du shader de texture du tutoriel précédent.

IV. Light.vs

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : light.vs
////////////////////////////////////////////////////////////////////////////////

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};

Les deux structures ont maintenant un float3 représentant le vecteur normal utilisé pour obtenir la quantité de lumière qui se calcule à partir de l'angle entre la direction de la normale et la direction de la lumière.

Sélectionnez
//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType LightVertexShader(VertexInputType input)
{
    PixelInputType output;

    // Change la quatrième composante du vecteur pour avoir des calculs matriciels corrects.

    // Calcule la position du sommet selon les matrices monde, de vue et de projection.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);

    // Stocke les coordonnées de la texture pour le pixel shader.
    output.tex = input.tex;

Le vecteur normal de ce sommet est calculé dans l'espace monde et normalisé avant d'être envoyé en entrée du pixel shader.

Sélectionnez
    // Calcule le vecteur normal à partir de la matrice monde seulement.
    output.normal = mul(input.normal, (float3x3)worldMatrix);

    // Normalise le vecteur normal.
    output.normal = normalize(output.normal);

    return output;
}

V. Light.ps

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : light.ps
////////////////////////////////////////////////////////////////////////////////

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
Texture2D shaderTexture;
SamplerState SampleType;

Nous avons deux nouvelles variables globales à l'intérieur du LightBuffer qui contiennent la couleur et la direction de la lumière diffuse. Elles sont définies à partir des valeurs du nouvel objet LightClass.

Sélectionnez
cbuffer LightBuffer
{
    float4 diffuseColor;
    float3 lightDirection;
    float padding;
};

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float3 normal : NORMAL;
};

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 LightPixelShader(PixelInputType input) : SV_TARGET
{
    float4 textureColor;
    float3 lightDir;
    float lightIntensity;
    float4 color;

    // Évalue la couleur du pixel à partir de la texture en utilisant l'échantillonneur aux coordonnées désignées.
    textureColor = shaderTexture.Sample(SampleType, input.tex);

C'est ici que l'équation d'éclairage mentionnée plus tôt est mise en œuvre. La valeur de l'intensité de la lumière est obtenue par le produit scalaire entre le vecteur normal du triangle et le vecteur de direction de lumière.

Sélectionnez
    // Inverse la direction de la lumière pour les calculs.
    lightDir = -lightDirection;

    // Calcule la quantité de lumière sur ce pixel.
    lightIntensity = saturate(dot(input.normal, lightDir));

Enfin, la valeur de la lumière diffuse est combinée avec la valeur du pixel de la texture pour produire la couleur finale.

Sélectionnez
    // Détermine la quantité finale de couleur diffuse en la combinant avec l'intensité de la lumière.
    color = saturate(diffuseColor * lightIntensity);

    // Multiplie le pixel de la texture et la couleur diffuse finale pour obtenir la couleur finale du pixel.
    color = color * textureColor;

    return color;
}

VI. Lightshaderclass.h

La nouvelle classe LightShaderClass est une légère réécriture de la classe TextureShaderClass des tutoriels précédents visant à intégrer l'éclairage.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTSHADERCLASS_H_
#define _LIGHTSHADERCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe: LightShaderClass
////////////////////////////////////////////////////////////////////////////////
class LightShaderClass
{
private:
        struct MatrixBufferType
        {
                D3DXMATRIX world;
                D3DXMATRIX view;
                D3DXMATRIX projection;
        };

La nouvelle structure LightBufferType sera utilisée pour stocker les informations sur l'éclairage, sa définition est la même que celle du pixel shader. Notez que j'ajoute un float complémentaire afin de m'assurer que la taille de la structure soit un multiple de 16. Sans cette précaution, la structure aurait tenu sur seulement 28 octets, et CreateBuffer aurait échoué si nous avions utilisé sizeof(LightBufferType) car il nécessite un multiple de 16 pour fonctionner correctement.

Sélectionnez
        struct LightBufferType
        {
                D3DXVECTOR4 diffuseColor;
                D3DXVECTOR3 lightDirection;
                float padding;  // Ajout d'un float complémentaire afin que la taille de la structure soit un multiple de 16 pour les besoins de la fonction CreateBuffer.
        };

public:
        LightShaderClass();
        LightShaderClass(const LightShaderClass&);
        ~LightShaderClass();

        bool Initialize(ID3D11Device*, HWND);
        void Shutdown();
        bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4);

private:
        bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
        void ShutdownShader();
        void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

        bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView*, D3DXVECTOR3, D3DXVECTOR4);
        void RenderShader(ID3D11DeviceContext*, int);

private:
        ID3D11VertexShader* m_vertexShader;
        ID3D11PixelShader* m_pixelShader;
        ID3D11InputLayout* m_layout;
        ID3D11SamplerState* m_sampleState;
        ID3D11Buffer* m_matrixBuffer;

Il y a un nouveau tampon constant privé porteur de l'information lumineuse (couleur et direction). Ce tampon de lumière sera utilisé par la classe pour définir les variables globales de lumière au sein du pixel shader HLSL.

Sélectionnez
        ID3D11Buffer* m_lightBuffer;
};

#endif

VII. Lightshaderclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightshaderclass.h"

LightShaderClass::LightShaderClass()
{
        m_vertexShader = 0;
        m_pixelShader = 0;
        m_layout = 0;
        m_sampleState = 0;
        m_matrixBuffer = 0;

Nous initialisons à la valeur nulle le tampon de lumière constant dans le constructeur de la classe.

Sélectionnez
        m_lightBuffer = 0;
}

LightShaderClass::LightShaderClass(const LightShaderClass& other)
{
}

LightShaderClass::~LightShaderClass()
{
}

bool LightShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
        bool result;

Les nouveaux fichiers shaders HLSL light.vs et light.ps sont utilisés comme entrées pour initialiser le shader de lumière.

Sélectionnez
        // Initialise les vertex et pixel shaders.
        result = InitializeShader(device, hwnd, L"../Engine/light.vs", L"../Engine/light.ps");
        if(!result)
        {
                return false;
        }

        return true;
}

void LightShaderClass::Shutdown()
{
        // Ferme les vertex et pixel shaders ainsi que les objets associés.
        ShutdownShader();

        return;
}

La méthode Render prend maintenant dans la direction et la couleur de la lumière diffuse comme entrées. Ces variables sont ensuite envoyées dans la méthode SetShaderParameters puis définies dans le shader lui-même.

Sélectionnez
bool LightShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                              D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection, D3DXVECTOR4 diffuseColor)
{
        bool result;

        // Définit les paramètres du shader qui seront utilisés pour le rendu.
        result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, texture, lightDirection, diffuseColor);
        if(!result)
        {
                return false;
        }

        // Effectue le rendu des tampons avec le shader.
        RenderShader(deviceContext, indexCount);

        return true;
}

bool LightShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
        HRESULT result;
        ID3D10Blob* errorMessage;
        ID3D10Blob* vertexShaderBuffer;
        ID3D10Blob* pixelShaderBuffer;

La variable polygonLayout a été modifiée pour comporter trois éléments au lieu de deux, afin qu'il puisse accueillir un vecteur normal dans la disposition.

Sélectionnez
        D3D11_INPUT_ELEMENT_DESC polygonLayout[3];
        unsigned int numElements;
        D3D11_SAMPLER_DESC samplerDesc;
        D3D11_BUFFER_DESC matrixBufferDesc;

Nous ajoutons aussi une nouvelle description de variable pour le tampon de lumière constant.

Sélectionnez
        D3D11_BUFFER_DESC lightBufferDesc;

        // Initialise à zéro les pointeurs que la méthode va utiliser.
        errorMessage = 0;
        vertexShaderBuffer = 0;
        pixelShaderBuffer = 0;

Nous chargeons le nouveau vertex shader de lumière.

Sélectionnez
        // Compile le code du vertex shader.
        result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "LightVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
                                       &vertexShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
        {
                // Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
                if(errorMessage)
                {
                        OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
                }
                // Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
                else
                {
                        MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
                }

                return false;
        }

Nous chargeons le nouveau pixel shader de lumière.

Sélectionnez
        // Compile le code du pixel shader.
        result = D3DX11CompileFromFile(psFilename, NULL, NULL, "LightPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
                                       &pixelShaderBuffer, &errorMessage, NULL);
        if(FAILED(result))
        {
                // Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
                if(errorMessage)
                {
                        OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
                }
                // Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
                else
                {
                        MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
                }

                return false;
        }

        // Crée le vertex shader à partir du tampon.
        result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
        if(FAILED(result))
        {
                return false;
        }

        // Crée le pixel shader à partir du tampon.
        result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
        if(FAILED(result))
        {
                return false;
        }

        // Crée la description de la disposition d'entrée du vertex.
        // Cette disposition doit être la même que celle de la structure VextexType du shader.
        polygonLayout[0].SemanticName = "POSITION";
        polygonLayout[0].SemanticIndex = 0;
        polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
        polygonLayout[0].InputSlot = 0;
        polygonLayout[0].AlignedByteOffset = 0;
        polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[0].InstanceDataStepRate = 0;

        polygonLayout[1].SemanticName = "TEXCOORD";
        polygonLayout[1].SemanticIndex = 0;
        polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
        polygonLayout[1].InputSlot = 0;
        polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
        polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[1].InstanceDataStepRate = 0;

L'un des principaux changements apportés à l'initialisation du shader se situe ici dans le polygonLayout. Nous ajoutons un troisième élément pour le vecteur normal qui sera utilisé pour l'éclairage. Son nom sémantique est NORMAL et son format est l'ordinaire DXGI_FORMAT_R32G32B32_FLOAT qui gère trois flottants pour les valeurs x, y et z du vecteur normal. La disposition correspondra dès lors à l'entrée du vertex shader HLSL attendue.

Sélectionnez
        polygonLayout[2].SemanticName = "NORMAL";
        polygonLayout[2].SemanticIndex = 0;
        polygonLayout[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
        polygonLayout[2].InputSlot = 0;
        polygonLayout[2].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
        polygonLayout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        polygonLayout[2].InstanceDataStepRate = 0;

        // Obtient le nombre d'éléments de la disposition des données.
        numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

        // Crée la disposition d'entrée du vertex.
        result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(),
                                           &m_layout);
        if(FAILED(result))
        {
                return false;
        }

        // Libère les vertex et pixel shaders, car nous n'en avons plus besoin.
        vertexShaderBuffer->Release();
        vertexShaderBuffer = 0;

        pixelShaderBuffer->Release();
        pixelShaderBuffer = 0;

        // Crée une description de l'état de l'échantillonneur de la texture.
        samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = 1;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
        samplerDesc.BorderColor[0] = 0;
        samplerDesc.BorderColor[1] = 0;
        samplerDesc.BorderColor[2] = 0;
        samplerDesc.BorderColor[3] = 0;
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        // Crée l'état de l'échantillonneur de la texture.
        result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
        if(FAILED(result))
        {
                return false;
        }

        // Met en place la description du tampon de matrice dynamique constant situé dans le vertex shader.
        matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
        matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
        matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        matrixBufferDesc.MiscFlags = 0;
        matrixBufferDesc.StructureByteStride = 0;

        // Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
        result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
        if(FAILED(result))
        {
                return false;
        }

Nous configurons ici la description du tampon de lumière constant qui gérera la couleur et la direction de la lumière diffuse. Faites attention à la taille des tampons constants, si ce n'est pas un multiple de 16, vous avez besoin de remplir l'espace manquant vers la fin ou la fonction CreateBuffer échouera. Dans notre cas, le tampon constant a une taille de 28 octets auxquels on ajoute 4 octets pour en obtenir 32.

Sélectionnez
        // Met en place la description du tampon de lumière constant du pixel shader.
        // Remarquez que le champ ByteWidth doit toujours être un multiple de 16
        // lorsque l'on utilise D3D11_BIND_CONSTANT_BUFFER ou l'utilisation de CreateBuffer échouera.
        lightBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
        lightBufferDesc.ByteWidth = sizeof(LightBufferType);
        lightBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        lightBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
        lightBufferDesc.MiscFlags = 0;
        lightBufferDesc.StructureByteStride = 0;

        // Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
        result = device->CreateBuffer(&lightBufferDesc, NULL, &m_lightBuffer);
        if(FAILED(result))
        {
                return false;
        }

        return true;
}

void LightShaderClass::ShutdownShader()
{

Le nouveau tampon de lumière constant est libéré dans la méthode ShutdownShader.

Sélectionnez
        // Libère le tampon de lumière constant.
        if(m_lightBuffer)
        {
                m_lightBuffer->Release();
                m_lightBuffer = 0;
        }

        // Libère le tampon de matrice constant.
        if(m_matrixBuffer)
        {
                m_matrixBuffer->Release();
                m_matrixBuffer = 0;
        }

        // Libère l'état de l'échantillonneur.
        if(m_sampleState)
        {
                m_sampleState->Release();
                m_sampleState = 0;
        }

        // Libère la disposition.
        if(m_layout)
        {
                m_layout->Release();
                m_layout = 0;
        }

        // Libère le pixel shader.
        if(m_pixelShader)
        {
                m_pixelShader->Release();
                m_pixelShader = 0;
        }

        // Libère le vertex shader.
        if(m_vertexShader)
        {
                m_vertexShader->Release();
                m_vertexShader = 0;
        }

        return;
}

void LightShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
        char* compileErrors;
        unsigned long bufferSize, i;
        ofstream fout;

        // Récupère le pointeur vers le message d'erreur du tampon.
        compileErrors = (char*)(errorMessage->GetBufferPointer());

        // Obtient la longueur du message.
        bufferSize = errorMessage->GetBufferSize();

        // Ouvre un fichier pour y écrire le message d'erreur.
        fout.open("shader-error.txt");

        // Écrit le message d'erreur.
        for(i=0; i<bufferSize; i++)
        {
                fout << compileErrors[i];
        }

        // Ferme le fichier.
        fout.close();

        // Libère le message d'erreur.
        errorMessage->Release();
        errorMessage = 0;

        // Affiche un message à l'écran pour avertir l'utilisateur qu'il doit vérifier
        // le contenu du fichier pour y trouver les erreurs de compilation.
        MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

        return;
}

La méthode SetShaderParameters prend maintenant en entrée les variables lightDirection et diffuseColor.

Sélectionnez
bool LightShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
                                           D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView* texture, D3DXVECTOR3 lightDirection,
                                           D3DXVECTOR4 diffuseColor)
{
        HRESULT result;
        D3D11_MAPPED_SUBRESOURCE mappedResource;
        unsigned int bufferNumber;
        MatrixBufferType* dataPtr;
        LightBufferType* dataPtr2;

        // Transpose les matrices afin de les préparer pour le shader.
        D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
        D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
        D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

        // Pose un verrou sur le tampon constant afin que l'on puise écrire dedans.
        result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
        if(FAILED(result))
        {
                return false;
        }

        // Récupère le pointeur des données dans le tampon constant.
        dataPtr = (MatrixBufferType*)mappedResource.pData;

        // Copie les matrices dans le tampon constant.
        dataPtr->world = worldMatrix;
        dataPtr->view = viewMatrix;
        dataPtr->projection = projectionMatrix;

        // Déverrouille le tampon constant.
        deviceContext->Unmap(m_matrixBuffer, 0);

        // Définit la position du tampon constant dans le vertex shader.
        bufferNumber = 0;

        // Fixe le tampon constant contenant les données à jour dans le vertex shader.
        deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

        // Définit la texture dans le pixel shader.
        deviceContext->PSSetShaderResources(0, 1, &texture);

Le tampon de lumière constant est configuré de la même manière que le tampon de matrice constant. Nous verrouillons d'abord le tampon et obtenons un pointeur vers lui permettant de définir la couleur et la direction de la lumière diffuse. Ceci fait, nous déverrouillons le tampon, et le transmettons au pixel shader. Notez que nous utilisons la fonction PSSetConstantBuffers au lieu de VSSetConstantBuffers puisqu'il s'agit d'un tampon de pixel shader.

Sélectionnez
        // Pose un verrou sur le tampon de lumière constant afin que l'on puisse écrire dedans.
        result = deviceContext->Map(m_lightBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
        if(FAILED(result))
        {
                return false;
        }

        // Récupère le pointeur des données dans le tampon constant.
        dataPtr2 = (LightBufferType*)mappedResource.pData;

        // Copie les variables de lumière dans le tampon constant.
        dataPtr2->diffuseColor = diffuseColor;
        dataPtr2->lightDirection = lightDirection;
        dataPtr2->padding = 0.0f;

        // Déverrouille le tampon constant.
        deviceContext->Unmap(m_lightBuffer, 0);

        // Définit la position du tampon de lumière constant dans le pixel shader.
        bufferNumber = 0;

        // Fixe finalement le tampon de lumière constant contenant les données à jour dans le pixel shader.
        deviceContext->PSSetConstantBuffers(bufferNumber, 1, &m_lightBuffer);

        return true;
}

void LightShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
        // Définit la disposition des données en entrée du vertex.
        deviceContext->IASetInputLayout(m_layout);

        // Définit les vertex et pixel shaders qui seront utilisés pour le rendu du triangle.
        deviceContext->VSSetShader(m_vertexShader, NULL, 0);
        deviceContext->PSSetShader(m_pixelShader, NULL, 0);

        // Définit l'état de l'échantillonneur dans le pixel shader.
        deviceContext->PSSetSamplers(0, 1, &m_sampleState);

        // Effectue le rendu du triangle.
        deviceContext->DrawIndexed(indexCount, 0, 0);

        return;
}

VIII. Modelclass.h

La classe ModelClass a été légèrement modifiée pour gérer les composantes de lumière.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>

/////////////////////////////
// INCLUSIONS DE LA CLASSE //
/////////////////////////////
#include "textureclass.h"

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:

La structure VertexType possède maintenant un vecteur normal pour l'éclairage.

Sélectionnez
        struct VertexType
        {
                D3DXVECTOR3 position;
                D3DXVECTOR2 texture;
                D3DXVECTOR3 normal; // NOUVEAU
        };

public:
        ModelClass();
        ModelClass(const ModelClass&);
        ~ModelClass();

        bool Initialize(ID3D11Device*, WCHAR*);
        void Shutdown();
        void Render(ID3D11DeviceContext*);

        int GetIndexCount();
        ID3D11ShaderResourceView* GetTexture();

private:
        bool InitializeBuffers(ID3D11Device*);
        void ShutdownBuffers();
        void RenderBuffers(ID3D11DeviceContext*);

        bool LoadTexture(ID3D11Device*, WCHAR*);
        void ReleaseTexture();

private:
        ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
        int m_vertexCount, m_indexCount;
        TextureClass* m_Texture;
};

#endif

IX. Modelclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"

ModelClass::ModelClass()
{
        m_vertexBuffer = 0;
        m_indexBuffer = 0;
        m_Texture = 0;
}

ModelClass::ModelClass(const ModelClass& other)
{
}

ModelClass::~ModelClass()
{
}

bool ModelClass::Initialize(ID3D11Device* device, WCHAR* textureFilename)
{
        bool result;

        // Initialise les tampons de sommets et d'indices.
        result = InitializeBuffers(device);
        if(!result)
        {
                return false;
        }

        // Charge la texture pour ce modèle.
        result = LoadTexture(device, textureFilename);
        if(!result)
        {
                return false;
        }

        return true;
}

void ModelClass::Shutdown()
{
        // Libère la texture du modèle.
        ReleaseTexture();

        // Libère les tampons de sommets et d'indices.
        ShutdownBuffers();

        return;
}

void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
        // Place les tampons de sommets et d'indices sur le pipeline graphique pour les préparer à être dessinés.
        RenderBuffers(deviceContext);

        return;
}

int ModelClass::GetIndexCount()
{
        return m_indexCount;
}

ID3D11ShaderResourceView* ModelClass::GetTexture()
{
        return m_Texture->GetTexture();
}

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
        VertexType* vertices;
        unsigned long* indices;
        D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
        D3D11_SUBRESOURCE_DATA vertexData, indexData;
        HRESULT result;

        // Fixe la taille du tableau de sommets.
        m_vertexCount = 3;

        // Fixe la taille du tableau d'indices.
        m_indexCount = 3;

        // Crée le tableau de sommets.
        vertices = new VertexType[m_vertexCount];
        if(!vertices)
        {
                return false;
        }

        // Crée le tableau d'indices.
        indices = new unsigned long[m_indexCount];
        if(!indices)
        {
                return false;
        }

Le seul changement au sein de la méthode InitializeBuffers se situe dans la définition du sommet. Chaque sommet a maintenant une normale servant aux calculs de l'éclairage. La normale est une ligne perpendiculaire à la face du polygone, de sorte à pouvoir déterminer l'orientation exacte de la face. Par souci de simplicité, j'ai réglé la normale de chaque sommet le long de l'axe Z avec une composante de Z à -1.0f qui l'oriente vers le spectateur.

Sélectionnez
        // Charge les données dans le tableau de sommets.
        vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // En bas à gauche.
        vertices[0].texture = D3DXVECTOR2(0.0f, 1.0f);
        vertices[0].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // NOUVEAU

        vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // En haut au milieu.
        vertices[1].texture = D3DXVECTOR2(0.5f, 0.0f);
        vertices[1].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // NOUVEAU

        vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // En bas à droite.
        vertices[2].texture = D3DXVECTOR2(1.0f, 1.0f);
        vertices[2].normal = D3DXVECTOR3(0.0f, 0.0f, -1.0f); // NOUVEAU

        // Charge les données dans le tableau d'indices.
        indices[0] = 0;  // En bas à gauche.
        indices[1] = 1;  // En haut au milieu.
        indices[2] = 2;  // En bas à droite.

        // Met en place la description du tampon de sommets.
        vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;
        vertexBufferDesc.MiscFlags = 0;
        vertexBufferDesc.StructureByteStride = 0;

        // Donne à la structure de sous-ressources un pointeur vers le tableau des sommets.
        vertexData.pSysMem = vertices;
        vertexData.SysMemPitch = 0;
        vertexData.SysMemSlicePitch = 0;

        // Crée le tampon des sommets.
        result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Met en place la description du tampon statique des indices.
        indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
        indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;
        indexBufferDesc.StructureByteStride = 0;

        // Donne à la structure de sous-ressources un pointeur vers le tableau des indices.
        indexData.pSysMem = indices;
        indexData.SysMemPitch = 0;
        indexData.SysMemSlicePitch = 0;

        // Crée le tampon des indices.
        result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Libère les tableaux, maintenant que les tampons ont été créés et remplis.
        delete [] vertices;
        vertices = 0;

        delete [] indices;
        indices = 0;

        return true;
}

void ModelClass::ShutdownBuffers()
{
        // Libère le tampon d'indices.
        if(m_indexBuffer)
        {
                m_indexBuffer->Release();
                m_indexBuffer = 0;
        }

        // Libère le tampon de sommets.
        if(m_vertexBuffer)
        {
                m_vertexBuffer->Release();
                m_vertexBuffer = 0;
        }

        return;
}

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
        unsigned int stride;
        unsigned int offset;

        // Règle le pas et l'offset du tampon.
        stride = sizeof(VertexType);
        offset = 0;

        // Active le tampon des sommets dans l'assembleur d'entrées pour effectuer son rendu.
        deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

        // Active le tampon des indices dans l'assembleur d'entrées pour effectuer son rendu.
        deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

        // Définit le type de primitive qui doit être rendue depuis ce tampon de sommets, ici un triangle.
        deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        return;
}

bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
        bool result;

        // Crée l'objet texture.
        m_Texture = new TextureClass;
        if(!m_Texture)
        {
                return false;
        }

        // Initialise l'objet texture.
        result = m_Texture->Initialize(device, filename);
        if(!result)
        {
                return false;
        }

        return true;
}

void ModelClass::ReleaseTexture()
{
        // Libère l'objet texture.
        if(m_Texture)
        {
                m_Texture->Shutdown();
                delete m_Texture;
                m_Texture = 0;
        }

        return;
}

X. Lightclass.h

Maintenant, nous allons regarder la nouvelle classe LightClass qui reste très simple. Son seul rôle est de maintenir la direction et la couleur des lumières.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _LIGHTCLASS_H_
#define _LIGHTCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3dx10math.h>

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : LightClass
////////////////////////////////////////////////////////////////////////////////
class LightClass
{
public:
        LightClass();
        LightClass(const LightClass&);
        ~LightClass();

        void SetDiffuseColor(float, float, float, float);
        void SetDirection(float, float, float);

        D3DXVECTOR4 GetDiffuseColor();
        D3DXVECTOR3 GetDirection();

private:
        D3DXVECTOR4 m_diffuseColor;
        D3DXVECTOR3 m_direction;
};

#endif

XI. Lightclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : lightclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "lightclass.h"

LightClass::LightClass()
{
}

LightClass::LightClass(const LightClass& other)
{
}

LightClass::~LightClass()
{
}

void LightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
        m_diffuseColor = D3DXVECTOR4(red, green, blue, alpha);
        return;
}

void LightClass::SetDirection(float x, float y, float z)
{
        m_direction = D3DXVECTOR3(x, y, z);
        return;
}

D3DXVECTOR4 LightClass::GetDiffuseColor()
{
        return m_diffuseColor;
}

D3DXVECTOR3 LightClass::GetDirection()
{
        return m_direction;
}

XII. Graphicsclass.h

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

////////////////////////////
// INCLUSIONS DE LA CLASSE//
////////////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"

La classe GraphicsClass inclut deux nouveaux fichiers d'en-tête des classes LightShaderClass et LightClass.

Sélectionnez
#include "lightshaderclass.h" // NOUVEAU
#include "lightclass.h" // NOUVEAU

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
        GraphicsClass();
        GraphicsClass(const GraphicsClass&);
        ~GraphicsClass();

        bool Initialize(int, int, HWND);
        void Shutdown();
        bool Frame();

private:

La méthode Render prend une entrée de type float.

Sélectionnez
        bool Render(float); // NOUVEAU

private:
        D3DClass* m_D3D;
        CameraClass* m_Camera;
        ModelClass* m_Model;

Il y a deux nouveaux attributs privés pour le shader de lumière et l'objet lumière.

Sélectionnez
        LightShaderClass* m_LightShader; // NOUVEAU
        LightClass* m_Light; // NOUVEAU
};

#endif

XIII. Graphicsclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

GraphicsClass::GraphicsClass()
{
        m_D3D = 0;
        m_Camera = 0;
        m_Model = 0;

Le shader de lumière et l'objet lumière sont initialisés à la valeur nulle dans le constructeur de la classe.

Sélectionnez
        m_LightShader = 0; // NOUVEAU
        m_Light = 0; // NOUVEAU
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
        bool result;

        // Crée l'objet Direct3D.
        m_D3D = new D3DClass;
        if(!m_D3D)
        {
                return false;
        }

        // Initialise l'objet Direct3D.
        result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
                return false;
        }

        // Crée l'objet Camera.
        m_Camera = new CameraClass;
        if(!m_Camera)
        {
                return false;
        }

        // Règle la position initiale de la caméra.
        m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

        // Crée l'objet Model.
        m_Model = new ModelClass;
        if(!m_Model)
        {
                return false;
        }

        // Initialise l'objet Model.
        result = m_Model->Initialize(m_D3D->GetDevice(), L"../Engine/data/seafloor.dds");
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
                return false;
        }

Le nouvel objet shader de lumière est créé et initialisé ici.

Sélectionnez
        // NOUVEAU - crée l'objet shader de lumière.
        m_LightShader = new LightShaderClass;
        if(!m_LightShader)
        {
                return false;
        }

        // NOUVEAU - initialise l'objet shader de lumière
        result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
                return false;
        }

Le nouvel objet lumière est créé ici.

Sélectionnez
        // NOUVEAU - crée l'objet lumière.
        m_Light = new LightClass;
        if(!m_Light)
        {
                return false;
        }

Nous définissons une couleur de lumière violette, et l'orientons selon l'axe Z positif.

Sélectionnez
        // NOUVEAU - initialise l'objet lumière.
        m_Light->SetDiffuseColor(1.0f, 0.0f, 1.0f, 1.0f);
        m_Light->SetDirection(0.0f, 0.0f, 1.0f);

        return true;
}

void GraphicsClass::Shutdown()
{

La méthode Shutdown libère les deux nouveaux objets.

Sélectionnez
        // NOUVEAU - libère l'objet lumière.
        if(m_Light)
        {
                delete m_Light;
                m_Light = 0;
        }

        // NOUVEAU - libère l'objet shader de lumière.
        if(m_LightShader)
        {
                m_LightShader->Shutdown();
                delete m_LightShader;
                m_LightShader = 0;
        }

        // Libère l'objet Model.
        if(m_Model)
        {
                m_Model->Shutdown();
                delete m_Model;
                m_Model = 0;
        }

        // Libère l'objet Caméra.
        if(m_Camera)
        {
                delete m_Camera;
                m_Camera = 0;
        }

        // Libère l'objet Direct3D.
        if(m_D3D)
        {
                m_D3D->Shutdown();
                delete m_D3D;
                m_D3D = 0;
        }

        return;
}

bool GraphicsClass::Frame()
{
        bool result;

Nous ajoutons une nouvelle variable statique afin de conserver l'angle de rotation et le mettre à jour à chaque trame, elle sera passée à la méthode Render.

Sélectionnez
        static float rotation = 0.0f; // NOUVEAU

        // NOUVEAU - met à jour l'angle de rotation lors de chaque trame.
        rotation += (float)D3DX_PI * 0.01f;
        if(rotation > 360.0f)
        {
                rotation -= 360.0f;
        }

        // Effectue le rendu de la scène.
        result = Render(rotation); // NOUVEAU
        if(!result)
        {
                return false;
        }

        return true;
}

bool GraphicsClass::Render(float rotation)
{
        D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
        bool result;

        // Efface les tampons pour débuter la scène.
        m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

        // Génère la matrice de vue à partir de la position de la caméra.
        m_Camera->Render();

        // Récupère les matrices monde, de vue et de projection depuis les objets Caméra et D3D.
        m_Camera->GetViewMatrix(viewMatrix);
        m_D3D->GetWorldMatrix(worldMatrix);
        m_D3D->GetProjectionMatrix(projectionMatrix);

Ici, nous appliquons une rotation à la matrice monde de sorte que lorsque nous effectuerons le rendu du triangle à l'aide de cette matrice, il sera tourné par la valeur de l'angle.

Sélectionnez
        // NOUVEAU - applique une rotation à la matrice monde afin de faire tourner le triangle.
        D3DXMatrixRotationY(&worldMatrix, rotation);

        // Place les tampons des sommets et d'indices du modèle dans le pipeline graphique pour les préparer à être rendus.
        m_Model->Render(m_D3D->GetDeviceContext());

Le shader de lumière est appelé ici pour effectuer le rendu du triangle. Le nouvel objet de lumière est utilisé pour envoyer la couleur et la direction de la lumière diffuse dans la méthode Render de sorte que le shader ait accès à ces valeurs.

Sélectionnez
        // NOUVEAU - effectue le rendu du modèle avec le shader de lumière.
        result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
                                       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
        if(!result)
        {
                return false;
        }

        // Affiche la scène rendue à l'écran.
        m_D3D->EndScene();

        return true;
}

XIV. Résumé

Avec peu de modifications du code, nous avons pu mettre en œuvre un éclairage directionnel de base. Assurez-vous de comprendre le fonctionnement des vecteurs normaux et leur importance dans le calcul de l'éclairage sur les faces des polygones. Notez que l'arrière du triangle en rotation n'est pas éclairé puisque le back face culling est activé dans notre classe D3DClass.

Image non disponible

XV. Exercices à faire

  1. Recompilez le projet et assurez-vous d'obtenir un triangle texturé en rotation qui est éclairé par une lumière violette. Appuyez sur Echap pour quitter ;
  2. Commentez la ligne color = couleur * textureColor; dans le pixel shader de sorte que la texture shaderTexture ne soit plus utilisée, vous devriez voir l'effet de la lumière sans la texture ;
  3. Mettez une couleur de lumière verte à la ligne m_Light->SetDiffuseColor dans le code de la classe GraphicsClass ;
  4. Changez la direction de la lumière selon l'axe des X positif et négatif. Vous pouvez aussi modifier la vitesse de rotation.

XVI. Source Code

  • Projet Visual Studio 2010 : dx11tut06.zip
  • Source seule : dx11src06.zip
  • Exécutable seul : dx11exe06.zip

XVII. Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek.

Merci à LittleWhite pour sa relecture lors de la traduction et f-leb pour sa relecture orthographique.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
Developpez.com

Copyright © 2000-2015 - www.developpez.com


ASM Mode Reel

DirectX 11 - Tutoriel 7 : Rendu de modèles 3D

Table des matières

  • Navigation
  • I. Introduction
  • II. Cube.txt
  • III. Modelclass.h
  • IV. Modelclass.cpp
  • V. Graphicsclass.h
  • VI. Graphicsclass.cpp
  • VII. Résumé
  • VIII. Exercices à faire
  • IX. Code source
  • X. Remerciements
  • Navigation

Ce tutoriel portera sur le rendu de modèles 3D dans DirectX 11 en utilisant HLSL. Le code de ce tutoriel est basé sur celui du Image non disponible tutoriel sur l'éclairage diffus.

I. Introduction

Nous avons déjà effectué le rendu de modèles 3D dans les tutoriels précédents, mais ils n'étaient composés que d'un seul triangle, ce qui n'était pas très intéressant. Maintenant que nous avons vu les bases, nous allons aller de l'avant pour effectuer le rendu d'un objet plus complexe : un cube. Avant d'aborder la façon de rendre les modèles plus complexes, nous allons parler des formats de modèles.

Il existe de nombreux outils permettant aux utilisateurs de créer des modèles 3D. Maya et 3D Studio Max sont les deux programmes de modélisation 3D les plus populaires. Il y a beaucoup d'autres outils avec moins de fonctionnalités, mais qui gèrent très bien les bases dont nous avons besoin.

Peu importe l'outil que vous choisissez d'utiliser, ils exporteront tous leurs modèles dans de nombreux formats différents. Je vous suggère de créer votre propre format et d'écrire un analyseur capable de convertir leur format d'export vers le vôtre. La raison en est que le logiciel de modélisation 3D que vous utilisez peut changer avec le temps, de même que son format de modèle. Aussi, utiliserez-vous peut-être plusieurs logiciels de modélisation 3D et aurez donc différents formats à traiter. Donc, si vous convertissez tous ces formats vers votre unique format, votre code n'aura jamais besoin de changer. Vous aurez juste à modifier vos convertisseurs permettant de traduire ces formats vers le vôtre. La plupart des logiciels de modélisation 3D exportent aussi une tonne de déchets, nécessaires au logiciel, mais dont nous n'avons aucune utilité, et que nous ne reportons donc pas dans notre format de modèle.
La partie la plus importante à retenir pour faire votre propre format est qu'il doit couvrir tout ce que vous avez besoin de faire et qu'il doit rester simple d'utilisation. Vous pouvez également envisager de faire différents formats pour des objets différents, certains pouvant contenir des données d'animation, d'autres pouvant être statiques, et ainsi de suite.

Le format de modèle que je vais vous présenter est très basique. Il contiendra une ligne pour chaque sommet du modèle. Chaque ligne correspondant au format de sommet utilisé dans le code qui sera vecteur de position (x, y, z), les coordonnées de texture (tu, tv) et la normale (nx, ny, nz). Le format contiendra également le nombre de sommets au début, de sorte à pouvoir lire la première ligne et allouer la mémoire nécessaire pour les structures, avant d'effectuer la lecture des données. Le format exige que chaque ligne représente un triangle et que les sommets soient présentés dans le sens horaire. Voici le fichier du cube dont nous allons effectuer le rendu :

II. Cube.txt

Sélectionnez
Vertex Count: 36

Data:

-1.0  1.0 -1.0 0.0 0.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
-1.0 -1.0 -1.0 0.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 1.0 0.0  0.0  0.0 -1.0
 1.0 -1.0 -1.0 1.0 1.0  0.0  0.0 -1.0
 1.0  1.0 -1.0 0.0 0.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0 -1.0 -1.0 0.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 1.0 0.0  1.0  0.0  0.0
 1.0 -1.0  1.0 1.0 1.0  1.0  0.0  0.0
 1.0  1.0  1.0 0.0 0.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
 1.0 -1.0  1.0 0.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 1.0 0.0  0.0  0.0  1.0
-1.0 -1.0  1.0 1.0 1.0  0.0  0.0  1.0
-1.0  1.0  1.0 0.0 0.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0 -1.0  1.0 0.0 1.0 -1.0  0.0  0.0
-1.0  1.0 -1.0 1.0 0.0 -1.0  0.0  0.0
-1.0 -1.0 -1.0 1.0 1.0 -1.0  0.0  0.0
-1.0  1.0  1.0 0.0 0.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
-1.0  1.0 -1.0 0.0 1.0  0.0  1.0  0.0
 1.0  1.0  1.0 1.0 0.0  0.0  1.0  0.0
 1.0  1.0 -1.0 1.0 1.0  0.0  1.0  0.0
-1.0 -1.0 -1.0 0.0 0.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
-1.0 -1.0  1.0 0.0 1.0  0.0 -1.0  0.0
 1.0 -1.0 -1.0 1.0 0.0  0.0 -1.0  0.0
 1.0 -1.0  1.0 1.0 1.0  0.0 -1.0  0.0

Comme vous pouvez le voir il y a 36 lignes de données x, y, z, tu, tv, nx, ny et nz. Chaque groupe de trois lignes compose un triangle, ce qui nous donne douze triangles formant un cube. Le format est très simple et peut être lu directement dans nos tampons de sommets et rendu sans aucune modification.

Faites attention au fait que certains programmes de modélisation 3D exportent leurs données dans des ordres différents, comme les systèmes de coordonnées directs et indirects. Rappelez-vous que par défaut, dans DirectX 11, le système de coordonnées est indirect et que les données du modèle doivent donc suivre. Restez attentif à ces différences et assurez-vous que votre programme d'analyse puisse gérer la conversion des données dans le bon format/ordre.

III. Modelclass.h

Pour ce tutoriel, la classe ModelClass ne nécessite que quelques modifications mineures, afin qu'elle effectue le rendu de modèles 3D à partir de nos fichiers texte de modèle.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>

La bibliothèque fstream est maintenant incluse pour pouvoir effectuer la lecture de nos fichiers.

Sélectionnez
#include <fstream>
using namespace std;

/////////////////////////////
// INCLUSIONS DE MA CLASSE //
/////////////////////////////
#include "textureclass.h"

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
        struct VertexType
        {
                D3DXVECTOR3 position;
                D3DXVECTOR2 texture;
                D3DXVECTOR3 normal;
        };

La modification suivante concerne l'ajout d'une nouvelle structure nommée ModelType pour représenter le format du modèle. Elle contient la position du sommet, les coordonnées de la texture et les normales : les mêmes que notre format de fichier.

Sélectionnez
        struct ModelType // NOUVEAU
        {
                float x, y, z;
                float tu, tv;
                float nx, ny, nz;
        };

public:
        ModelClass();
        ModelClass(const ModelClass&);
        ~ModelClass();

La méthode Initialize prend dorénavant en paramètre le nom du fichier du modèle à charger.

Sélectionnez
        bool Initialize(ID3D11Device*, char*, WCHAR*); // MODIFIÉ
        void Shutdown();
        void Render(ID3D11DeviceContext*);

        int GetIndexCount();
        ID3D11ShaderResourceView* GetTexture();

private:
        bool InitializeBuffers(ID3D11Device*);
        void ShutdownBuffers();
        void RenderBuffers(ID3D11DeviceContext*);

        bool LoadTexture(ID3D11Device*, WCHAR*);
        void ReleaseTexture();

Nous rajoutons deux méthodes permettant de charger les données du fichier et de les libérer.

Sélectionnez
        bool LoadModel(char*); // NOUVEAU
        void ReleaseModel();   // NOUVEAU

private:
        ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
        int m_vertexCount, m_indexCount;
        TextureClass* m_Texture;

Nous ajoutons enfin un nouveau membre privé nommé m_model qui sera le pointeur vers le tableau de structures ModelType. Cette variable sera utilisée pour lire et stocker les données du modèle avant qu'elles soient placées dans le tampon de sommets.

Sélectionnez
        ModelType* m_model; // NOUVEAU
};

#endif

IV. Modelclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"

ModelClass::ModelClass()
{
        m_vertexBuffer = 0;
        m_indexBuffer = 0;
        m_Texture = 0;

Le nouveau pointeur est initialisé à la valeur nulle dans le constructeur de la classe.

Sélectionnez
        m_model = 0; // NOUVEAU
}

ModelClass::ModelClass(const ModelClass& other)
{
}

ModelClass::~ModelClass()
{
}

La méthode Initialize prend dorénavant le nom du fichier du modèle à charger.

Sélectionnez
bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename) // MODIFIÉ
{
        bool result;

Dans la méthode Initialize, nous appelons maintenant en premier la nouvelle méthode loadModel. Elle va charger les données du modèle à partir du nom du fichier passé en argument dans le nouveau tableau m_model. Une fois ce tableau modèle rempli, nous pouvons construire les tampons de sommets et d'indices. Puisque InitializeBuffers dépend maintenant de ce modèle de données, assurez-vous d'appeler les méthodes dans le bon ordre.

Sélectionnez
        // NOUVEAU - charge les données du modèle.
        result = LoadModel(modelFilename);
        if(!result)
        {
                return false;
        }

        // Initialise les tampons de sommets et d'indices.
        result = InitializeBuffers(device);
        if(!result)
        {
                return false;
        }

        // Charge la texture pour ce modèle.
        result = LoadTexture(device, textureFilename);
        if(!result)
        {
                return false;
        }

        return true;
}

void ModelClass::Shutdown()
{
        // Libère la texture du modèle.
        ReleaseTexture();

        // Libère les tampons de sommets et d'indices.
        ShutdownBuffers();

Nous ajoutons un appel à ReleaseModel dans la méthode Shutdown afin de libérer le pointeur m_model une fois terminé.

Sélectionnez
        // NOUVEAU - libère les données du modèle.
        ReleaseModel();

        return;
}

void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
        // Place les tampons de sommets et d'indices sur le pipeline graphique pour les préparer à être dessinés.
        RenderBuffers(deviceContext);

        return;
}

int ModelClass::GetIndexCount()
{
        return m_indexCount;
}

ID3D11ShaderResourceView* ModelClass::GetTexture()
{
        return m_Texture->GetTexture();
}

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
        VertexType* vertices;
        unsigned long* indices;
        D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
        D3D11_SUBRESOURCE_DATA vertexData, indexData;
        HRESULT result;
        int i;

Notez que nous ne fixons plus manuellement le nombre de sommets et d'indices ici. Vous verrez plus tard que nous récupérons cette valeur dans la méthode ModelClass::loadModel.

Sélectionnez
        // Crée le tableau de sommets.
        vertices = new VertexType[m_vertexCount];
        if(!vertices)
        {
                return false;
        }

        // Crée le tableau d'indices.
        indices = new unsigned long[m_indexCount];
        if(!indices)
        {
                return false;
        }

Le chargement des sommets et des indices des tableaux a légèrement changé. Au lieu de définir manuellement les valeurs, nous copions en boucle tous les éléments du nouveau tableau pointé par m_model dans le tableau de sommets. Le tableau d'indices est facile à construire car chaque sommet a le même indice que sa position dans le tableau où il a été chargé.

Sélectionnez
        // NOUVEAU - charge les tableaux de sommets et d'indices avec les données.
        for(i=0; i<m_vertexCount; i++)
        {
                vertices[i].position = D3DXVECTOR3(m_model[i].x, m_model[i].y, m_model[i].z);
                vertices[i].texture = D3DXVECTOR2(m_model[i].tu, m_model[i].tv);
                vertices[i].normal = D3DXVECTOR3(m_model[i].nx, m_model[i].ny, m_model[i].nz);

                indices[i] = i;
        }

        // Met en place la description du tampon de sommets.
        vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;
        vertexBufferDesc.MiscFlags = 0;
        vertexBufferDesc.StructureByteStride = 0;

        // Donne à la structure de sous-ressources un pointeur vers le tableau des sommets.
        vertexData.pSysMem = vertices;
        vertexData.SysMemPitch = 0;
        vertexData.SysMemSlicePitch = 0;

        // Crée le tampon des sommets.
        result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Met en place la description du tampon statique des indices.
        indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
        indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;
        indexBufferDesc.StructureByteStride = 0;

        // Donne à la structure de sous-ressources un pointeur vers le tableau des indices.
        indexData.pSysMem = indices;
        indexData.SysMemPitch = 0;
        indexData.SysMemSlicePitch = 0;

        // Crée le tampon des indices.
        result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
        if(FAILED(result))
        {
                return false;
        }

        // Libère les tableaux, maintenant que les tampons ont été créés et remplis.
        delete [] vertices;
        vertices = 0;

        delete [] indices;
        indices = 0;

        return true;
}

void ModelClass::ShutdownBuffers()
{
        // Libère le tampon d'indices.
        if(m_indexBuffer)
        {
                m_indexBuffer->Release();
                m_indexBuffer = 0;
        }

        // Libère le tampon de sommets.
        if(m_vertexBuffer)
        {
                m_vertexBuffer->Release();
                m_vertexBuffer = 0;
        }

        return;
}

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
        unsigned int stride;
        unsigned int offset;

        // Règle le pas et l'offset du tampon.
        stride = sizeof(VertexType);
        offset = 0;

        // Active le tampon des sommets dans l'assembleur d'entrées pour effectuer son rendu.
        deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

        // Active le tampon des indices dans l'assembleur d'entrées pour effectuer son rendu.
        deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

        // Définit le type de primitives utilisé. Ces primitives, ici des triangles, seront affichées depuis le tampon de sommets.
        deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        return;
}

bool ModelClass::LoadTexture(ID3D11Device* device, WCHAR* filename)
{
        bool result;

        // Crée l'objet texture.
        m_Texture = new TextureClass;
        if(!m_Texture)
        {
                return false;
        }

        // Initialise l'objet texture.
        result = m_Texture->Initialize(device, filename);
        if(!result)
        {
                return false;
        }

        return true;
}

void ModelClass::ReleaseTexture()
{
        // Libère l'objet texture.
        if(m_Texture)
        {
                m_Texture->Shutdown();
                delete m_Texture;
                m_Texture = 0;
        }

        return;
}

Voici la nouvelle méthode loadModel qui gère le chargement des données du modèle dans le pointeur m_model à partir du fichier texte. Elle ouvre le fichier et commence par lire le nombre de sommets. Elle alloue alors le tableau de ModelType puis lit chaque ligne pour la mettre dans ce tableau. Le nombre de sommets et d'indices est dorénavant défini dans cette méthode.

Sélectionnez
bool ModelClass::LoadModel(char* filename)
{
        ifstream fin;
        char input;
        int i;

        // Ouvre le fichier du modèle.
        fin.open(filename);

        // Si le fichier n'a pas pu être ouvert, false est retourné.
        if(fin.fail())
        {
                return false;
        }

        // La chaîne « Vertex Count: » est passée.
        fin.get(input);
        while(input != ':')
        {
                fin.get(input);
        }

        // Lecture du nombre de sommets.
        fin >> m_vertexCount;

        // Le nombre d'indices et le nombre de sommets sont les mêmes.
        m_indexCount = m_vertexCount;

        // Création du modèle en utilisant la valeur lue.
        m_model = new ModelType[m_vertexCount];
        if(!m_model)
        {
                return false;
        }

        // Placement au début des données à lire.
        fin.get(input);
        while(input != ':')
        {
                fin.get(input);
        }
        fin.get(input);
        fin.get(input);

        // Lecture des données.
        for(i=0; i<m_vertexCount; i++)
        {
                fin >> m_model[i].x >> m_model[i].y >> m_model[i].z;
                fin >> m_model[i].tu >> m_model[i].tv;
                fin >> m_model[i].nx >> m_model[i].ny >> m_model[i].nz;
        }

        // Fermeture du fichier.
        fin.close();

        return true;
}

La méthode ReleaseModel se charge de libérer le tableau du modèle.

Sélectionnez
void ModelClass::ReleaseModel() // NOUVEAU
{
        if(m_model)
        {
                delete [] m_model;
                m_model = 0;
        }

        return;
}

V. Graphicsclass.h

L'en-tête de la classe GraphicsClass n'a subi aucun changement depuis le tutoriel précédent.

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

/////////////////////////////
// INCLUSIONS DE MA CLASSE //
/////////////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "lightshaderclass.h"
#include "lightclass.h"

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
        GraphicsClass();
        GraphicsClass(const GraphicsClass&);
        ~GraphicsClass();

        bool Initialize(int, int, HWND);
        void Shutdown();
        bool Frame();

private:
        bool Render(float);

private:
        D3DClass* m_D3D;
        CameraClass* m_Camera;
        ModelClass* m_Model;
        LightShaderClass* m_LightShader;
        LightClass* m_Light;
};

#endif

VI. Graphicsclass.cpp

Sélectionnez
////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

GraphicsClass::GraphicsClass()
{
        m_D3D = 0;
        m_Camera = 0;
        m_Model = 0;
        m_LightShader = 0;
        m_Light = 0;
}

GraphicsClass::GraphicsClass(const GraphicsClass& other)
{
}

GraphicsClass::~GraphicsClass()
{
}

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
        bool result;

        // Crée l'objet Direct3D.
        m_D3D = new D3DClass;
        if(!m_D3D)
        {
                return false;
        }

        // Initialise l'objet Direct3D.
        result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
                return false;
        }

        // Crée l'objet Camera.
        m_Camera = new CameraClass;
        if(!m_Camera)
        {
                return false;
        }

        // Règle la position initiale de la caméra.
        m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

        // Crée l'objet Model.
        m_Model = new ModelClass;
        if(!m_Model)
        {
                return false;
        }

La méthode Initialize du modèle prend maintenant le nom du fichier du modèle qu'il charge en paramètre. Dans ce tutoriel, nous utiliserons le fichier cube.txt depuis lequel sera chargé le cube 3D pour le rendu.

Sélectionnez
        // MODIFIÉ - initialise l'objet Model.
        result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/cube.txt", L"../Engine/data/seafloor.dds");
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
                return false;
        }

        // Crée l'objet shader de lumière.
        m_LightShader = new LightShaderClass;
        if(!m_LightShader)
        {
                return false;
        }

        // Initialise l'objet Model.
        result = m_LightShader->Initialize(m_D3D->GetDevice(), hwnd);
        if(!result)
        {
                MessageBox(hwnd, L"Could not initialize the light shader object.", L"Error", MB_OK);
                return false;
        }

        // Crée l'objet lumière.
        m_Light = new LightClass;
        if(!m_Light)
        {
                return false;
        }

Dans ce tutoriel, j'ai mis une couleur de lumière diffuse blanche.

Sélectionnez
        // Initialise l'objet lumière.
        m_Light->SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f); // MODIFIÉ
        m_Light->SetDirection(0.0f, 0.0f, 1.0f);

        return true;
}

void GraphicsClass::Shutdown()
{
        // Libère l'objet lumière.
        if(m_Light)
        {
                delete m_Light;
                m_Light = 0;
        }

        // Libère l'objet shader de lumière.
        if(m_LightShader)
        {
                m_LightShader->Shutdown();
                delete m_LightShader;
                m_LightShader = 0;
        }

        // Libère l'objet Model.
        if(m_Model)
        {
                m_Model->Shutdown();
                delete m_Model;
                m_Model = 0;
        }

        // Libère l'objet Camera.
        if(m_Camera)
        {
                delete m_Camera;
                m_Camera = 0;
        }

        // Libère l'objet Direct3D.
        if(m_D3D)
        {
                m_D3D->Shutdown();
                delete m_D3D;
                m_D3D = 0;
        }

        return;
}

bool GraphicsClass::Frame()
{
        bool result;
        static float rotation = 0.0f;

        // Met à jour l'angle de rotation lors de chaque trame.
        rotation += (float)D3DX_PI * 0.01f;
        if(rotation > 360.0f)
        {
                rotation -= 360.0f;
        }

        // Effectue le rendu de la scène.
        result = Render(rotation);
        if(!result)
        {
                return false;
        }

        return true;
}

bool GraphicsClass::Render(float rotation)
{
        D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
        bool result;

        // Efface les tampons pour débuter la scène.
        m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

        // Génère la matrice de vue à partir de la position de la caméra.
        m_Camera->Render();

        // Récupère les matrices monde, de vue et de projection depuis les objets Camera et D3D.
        m_Camera->GetViewMatrix(viewMatrix);
        m_D3D->GetWorldMatrix(worldMatrix);
        m_D3D->GetProjectionMatrix(projectionMatrix);

        // Applique une rotation à la matrice monde afin de faire tourner le triangle.
        D3DXMatrixRotationY(&worldMatrix, rotation);

        // Place les tampons des sommets et d'indices du modèle dans le pipeline graphique pour les préparer à être rendus.
        m_Model->Render(m_D3D->GetDeviceContext());

        // Effectue le rendu du modèle avec le shader de lumière.
        result = m_LightShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
                                       m_Model->GetTexture(), m_Light->GetDirection(), m_Light->GetDiffuseColor());
        if(!result)
        {
                return false;
        }

        // Affiche la scène rendue à l'écran.
        m_D3D->EndScene();

        return true;
}

VII. Résumé

Avec les modifications apportées à la classe ModelClass, nous pouvons maintenant charger des modèles 3D et effectuer leur rendu. Le format utilisé ici sert seulement pour les objets statiques de base avec éclairage, mais c'est un bon début pour appréhender le fonctionnement des modèles.

Image non disponible

VIII. Exercices à faire

  1. Recompilez le code et exécutez le programme. Vous devriez obtenir un cube en rotation avec la même texture seafloor.dds. Appuyez sur « Echap » pour quitter le programme une fois cela fait ;
  2. Procurez-vous un logiciel de modélisation 3D correct(1) (on l'espère quelque chose de gratuit), créez vos propres modèles simples puis exportez-les. Commencez à regarder leur format ;
  3. Écrivez un programme d'analyse syntaxique simple qui convertit les formats d'exportation au format utilisé ici. Remplacez le fichier cube.txt par votre modèle et exécutez le programme.

IX. Code source

  • Projet Visual Studio 2010 : dx11tut07.zip.
  • Source seule : dx11src07.zip.
  • Exécutable seul : dx11exe07.zip.

X. Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek.

Merci à LittleWhite pour sa relecture lors de la traduction et jacques_jean pour sa relecture orthographique.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
Developpez.com

Copyright © 2000-2015 - www.developpez.com


ASM Mode Reel

  • DirectX 11 - Tutoriel 8 : Chargement de modèles Maya 2011

    Table des matières Plier Déplier

    • Navigation
    • I. Introduction
    • II. Cube.obj
    • III. Conversion de système de coordonnées direct à indirect
    • IV. Main.cpp
    • V. Résumé
    • VI. Exercices à faire
    • VII. Code source
    • VIII. Remerciements
    • Navigation

    Ce tutoriel va vous expliquer comment importer des modèles 3D statiques depuis Maya 2011. Notez qu'il s'axe sur le logiciel Maya, mais qu'il peut facilement s'appliquer à n'importe quel autre logiciel de modélisation 3D, moyennant de légers changements.

    I. Introduction

    Dans les tutoriels précédents, nous avons créé notre propre format de modèle et affiché des modèles 3D en l'utilisant. L'objectif est maintenant de convertir des modèles Maya 2011 vers notre format et d'effectuer leur rendu. Je ne vais pas traiter la façon de modéliser des objets 3D avec Maya, il y a des centaines de tutoriels sur le Net et déjà consacrés à cela, nous allons commencer avec un modèle 3D texturé et triangulé prêt à l'exportation.
    Pour Maya, nous utilisons le format d'exportation .OBJ car il est facilement lisible et agréable pour ceux qui débutent.

    Pour exporter votre modèle vers le format .OBJ, vous devez d'abord activer l'exportateur .OBJ dans Maya. Cliquez sur « Window », puis « Settings/Preferences » et « Plug-in Manager ». Faites défiler jusqu'à objExport.mll et sélectionnez à la fois « Loaded » et « Auto Load ». Maintenant, pour exporter votre modèle dans ce format, cliquez sur « File », puis « Export All ». Puis sélectionnez vers le bas « Files of type: » et faites défiler pour sélectionner « OBJexport », donnez-lui un nom et cliquez sur « Export All » pour qu'il exporte votre modèle vers un fichier texte d'extension .obj. Pour consulter le fichier, vous pouvez faire un clic droit et sélectionner « Ouvrir avec… » puis choisir WordPad. Vous verrez alors quelque chose qui ressemble à ceci :

    II. Cube.obj

    Sélectionnez
    # This file uses centimeters as units for non-parametric coordinates.

mtllib cube.mtl
g default
v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.001992 0.001992
vt 0.998008 0.001992
vt 0.001992 0.998008
vt 0.998008 0.998008
vt 0.998008 0.998008
vt 0.001992 0.998008
vt 0.998008 0.001992
vt 0.001992 0.001992
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 0.000000 1.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 0.000000 -1.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000
s 1
g pCube1
usemtl file1SG
f 1/1/1 2/2/2 3/3/3
f 3/3/3 2/2/2 4/4/4
s 2
f 3/13/5 4/14/6 5/15/7
f 5/15/7 4/14/6 6/16/8
s 3
f 5/21/9 6/22/10 7/23/11
f 7/23/11 6/22/10 8/24/12
s 4
f 7/17/13 8/18/14 1/19/15
f 1/19/15 8/18/14 2/20/16
s 5
f 2/5/17 8/6/18 4/7/19
f 4/7/19 8/6/18 6/8/20
s 6
f 7/9/21 1/10/22 5/11/23
f 5/11/23 1/10/22 3/12/24

    Ce fichier de modèle .OBJ représente un cube 3D. Il est composé de 8 sommets, 24 coordonnées de texture et normales et 6 côtés composés de 12 faces au total. Lorsque vous lisez le fichier, vous pouvez ignorer toutes les lignes ne commençant pas par « V », « VT », « VN », ou « F ». Les informations supplémentaires ne seront pas nécessaires pour la conversion. Regardons ce que chacune des lignes importantes signifie :

    1. Les lignes « V » sont les sommets. Le cube est constitué de 8 sommets pour ses 8 coins. Chacun est représenté par le format flottant X,Y,Z ;
    2. Les lignes « VT » sont les coordonnées de texture. Le cube en possède 24, pour la plupart en double, car chaque sommet de chaque triangle du modèle est reporté. Ils sont représentés par le format flottant TU, TV ;
    3. Les lignes « VN » sont les normales. Le cube en a 24, également en double. Ils sont représentés par le format flottant NX, NY, NZ ;
    4. Les lignes « F » sont les faces de chaque triangle du cube. Les valeurs listées correspondent aux indices des sommets, coordonnées de texture et normales. Chaque face est représentée par :
    Sélectionnez
    f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3

    Ainsi une ligne f 3/13/5 4/14/6 5/15/7 se traduit par Sommet3/Texture13/Normale5 Sommet4/Texture14/Normale6 Sommet5/Texture15/Normale7.

    L'ordre dans lequel les données sont écrites est très important. Par exemple, le premier sommet correspond au Sommet1 dans la liste des faces. Il en va de même pour les coordonnées de texture et les normales.

    Remarquez que dans les lignes des faces, chaque triplet d'indices de chaque ligne, correspond à un triangle. Dans notre cas, le total des 12 faces constituent les 6 côtés du cube avec deux triangles de chaque côté.

    III. Conversion de système de coordonnées direct à indirect

    Par défaut, Maya 2011 travaille avec un système de coordonnées direct et exporte son fichier en le conservant. Afin de convertir les données vers un système indirect qu'utilise DirectX 11 par défaut, vous devez effectuer ce qui suit :

    1. Inverser la coordonnée Z des sommets. Vous verrez ceci vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f dans le code ;
    2. Inverser la coordonnée TV de texture. Vous verrez dans le code que je fais texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y ;
    3. Inverser la composante Z de la normale. Vous pourrez voir ce code normals[normalIndex].z = normals[normalIndex].z * -1.0f le faire ;
    4. Passer le dessin de l'ordre direct à l'ordre indirect. Dans le code, je lis simplement les indices dans l'ordre inverse plutôt que de les réorganiser après lecture.
    Sélectionnez
    fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3;
fin >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2;
fin >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1;

    Ces quatre étapes effectuées, les données du modèle sont prêtes à être rendues correctement sous DirectX 11.

    IV. Main.cpp

    Ce programme permettant le passage de fichiers Maya 2011 d'extension .obj vers notre format DirectX 11 est plutôt simple et est contenu au sein d'un seul fichier nommé main.cpp. Il ouvre une fenêtre de commande et demande le chemin du fichier du fichier .obj à convertir. Une fois le nom tapé, il va tenter d'ouvrir le fichier et, si le fichier s'ouvre, de lire la valeur du nombre de données, et mettre en place les structures requises servant à les stocker. Après quoi, il les charge et les convertit vers un système de coordonnées indirect. Ceci fait, il écrit les données dans un fichier de modèle .txt et ce dernier peut être renommé et utilisé pour le rendu dans DirectX 11 en appliquant le projet de rendu de modèles 3D du tutoriel précédent.

    Sélectionnez
    ////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : main.cpp
////////////////////////////////////////////////////////////////////////////////

////////////////
// INCLUSIONS //
////////////////
#include <iostream>
#include <fstream>
using namespace std;

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
typedef struct
{
    float x, y, z;
}VertexType;

typedef struct
{
    int vIndex1, vIndex2, vIndex3;
    int tIndex1, tIndex2, tIndex3;
    int nIndex1, nIndex2, nIndex3;
}FaceType;

//////////////////////////////
// PROTOTYPES DES FONCTIONS //
//////////////////////////////
void GetModelFilename(char*);
bool ReadFileCounts(char*, int&, int&, int&, int&);
bool LoadDataStructures(char*, int, int, int, int);

/////////////////////////
// PROGRAMME PRINCIPAL //
/////////////////////////
int main()
{
    bool result;
    char filename[256];
    int vertexCount, textureCount, normalCount, faceCount;
    char garbage;

    // Lit le nom du modèle.
    GetModelFilename(filename);

    // Lit le nombre de sommets, de coordonnées de texture, de normales et de
    // faces afin d'initialiser correctement la structure de données à la taille requise.
    result = ReadFileCounts(filename, vertexCount, textureCount, normalCount, faceCount);
    if(!result)
    {
        return -1;
    }

    // Affiche les quantités à l'écran à titre d'information.
    cout << endl;
    cout << "Vertices: " << vertexCount << endl;
    cout << "UVs:      " << textureCount << endl;
    cout << "Normals:  " << normalCount << endl;
    cout << "Faces:    " << faceCount << endl;

    // Charge les données dans la structure et les retourne vers notre format de modèle.
    result = LoadDataStructures(filename, vertexCount, textureCount, normalCount, faceCount);
    if(!result)
    {
        return -1;
    }

    // Avertit l'utilisateur que le modèle a été converti.
    cout << "\nFile has been converted." << endl;
    cout << "\nDo you wish to exit (y/n)? ";
    cin >> garbage;

    return 0;
}
    GetModelFilename
    Sélectionnez
    void GetModelFilename(char* filename)
{
    bool done;
    ifstream fin;

    // Boucle jusqu'à avoir le nom d'un fichier.
    done = false;
    while(!done)
    {
        // Demande le nom du fichier à l'utilisateur.
        cout << "Enter model filename: ";

        // Lit le nom du fichier.
        cin >> filename;

        // Essaie d'ouvrir le fichier.
        fin.open(filename);

        if(fin.good())
        {
                // Si le fichier existe et si aucun problème ne survient,
    // on quitte la fonction puisque l'on a obtenu le nom du fichier.
                done = true;
        }
        else
        {
                // Si le fichier n'existe pas ou qu'un problème est survenu à son ouverture, on avertit l'utilisateur et on recommence.
                fin.clear();
                cout << endl;
                cout << "File " << filename << " could not be opened." << endl << endl;
        }
    }

    return;
}
    ReadFileCounts
    Sélectionnez
    bool ReadFileCounts(char* filename, int& vertexCount, int& textureCount, int& normalCount, int& faceCount)
{
    ifstream fin;
    char input;

    // Initialise les compteurs.
    vertexCount = 0;
    textureCount = 0;
    normalCount = 0;
    faceCount = 0;

    // Ouvre le fichier.
    fin.open(filename);

    // Vérifie si le fichier a bien été ouvert.
    if(fin.fail() == true)
    {
        return false;
    }

    // Effectue la lecture du fichier jusqu'à atteindre la fin.
    fin.get(input);
    while(!fin.eof())
    {
        // Si la ligne commence par 'v', compte soit les sommets,
    // soit les coordonnées de texture, soit les normales.
        if(input == 'v')
        {
            fin.get(input);
            if(input == ' ') { vertexCount++; }
            if(input == 't') { textureCount++; }
            if(input == 'n') { normalCount++; }
        }

        // Si la ligne commence par 'f', incrémente le nombre de faces.
        if(input == 'f')
        {
            fin.get(input);
            if(input == ' ') { faceCount++; }
        }

        // Sinon, lit le reste de la ligne.
        while(input != '\n')
        {
            fin.get(input);
        }

        // Commence à lire le début de la ligne suivante.
        fin.get(input);
    }

    // Close the file.
    fin.close();

    return true;
}
    LoadDataStructures
    Sélectionnez
    bool LoadDataStructures(char* filename, int vertexCount, int textureCount, int normalCount, int faceCount)
{
    VertexType *vertices, *texcoords, *normals;
    FaceType *faces;
    ifstream fin;
    int vertexIndex, texcoordIndex, normalIndex, faceIndex, vIndex, tIndex, nIndex;
    char input, input2;
    ofstream fout;

    // Initialise la structure des quatre données.
    vertices = new VertexType[vertexCount];
    if(!vertices)
    {
        return false;
    }

    texcoords = new VertexType[textureCount];
    if(!texcoords)
    {
        return false;
    }

    normals = new VertexType[normalCount];
    if(!normals)
    {
        return false;
    }

    faces = new FaceType[faceCount];
    if(!faces)
    {
        return false;
    }

    // Initialise les indices.
    vertexIndex = 0;
    texcoordIndex = 0;
    normalIndex = 0;
    faceIndex = 0;

    // Ouvre le fichier.
    fin.open(filename);

    // Vérifie si le fichier a bien été ouvert.
    if(fin.fail() == true)
    {
        return false;
    }

    // Charge les sommets, coordonnées de texture, normales et faces dans la structure.
    // Important : effectue la conversion vers le système de coordonnées indirect puisque Maya utilise un système direct.
    fin.get(input);
    while(!fin.eof())
    {
        if(input == 'v')
        {
            fin.get(input);

            // Lit les sommets.
            if(input == ' ')
            {
                fin >> vertices[vertexIndex].x >> vertices[vertexIndex].y >> vertices[vertexIndex].z;

                // Inverse la composante Z du sommet pour passer en système indirect.
                vertices[vertexIndex].z = vertices[vertexIndex].z * -1.0f;
                vertexIndex++;
            }

            // Lit les coordonnées UV de texture.
            if(input == 't')
            {
                fin >> texcoords[texcoordIndex].x >> texcoords[texcoordIndex].y;

                // Inverse la composante V de la coordonnées de texture pour passer en système indirect.
                texcoords[texcoordIndex].y = 1.0f - texcoords[texcoordIndex].y;
                texcoordIndex++;
            }

            // Lit les normales.
            if(input == 'n')
            {
                fin >> normals[normalIndex].x >> normals[normalIndex].y >> normals[normalIndex].z;

                // Inverse la composante Z pour passer en système indirect.
                normals[normalIndex].z = normals[normalIndex].z * -1.0f;
                normalIndex++;
            }
        }

        // Lit les faces.
        if(input == 'f')
        {
            fin.get(input);
            if(input == ' ')
            {
                // Lit les données des faces à partir de la fin pour passer en système indirect.
                fin >> faces[faceIndex].vIndex3 >> input2 >> faces[faceIndex].tIndex3 >> input2 >> faces[faceIndex].nIndex3
                    >> faces[faceIndex].vIndex2 >> input2 >> faces[faceIndex].tIndex2 >> input2 >> faces[faceIndex].nIndex2
                    >> faces[faceIndex].vIndex1 >> input2 >> faces[faceIndex].tIndex1 >> input2 >> faces[faceIndex].nIndex1;
                faceIndex++;
            }
        }

        // Lit le reste de la ligne.
        while(input != '\n')
        {
            fin.get(input);
        }

        // Commence à lire la ligne suivante.
        fin.get(input);
    }

    // Ferme le fichier.
    fin.close();

    // Ouvre le fichier de sortie.
    fout.open("model.txt");

    // Écrit l'en-tête de notre format de modèle.
    fout << "Vertex Count: " << (faceCount * 3) << endl;
    fout << endl;
    fout << "Data:" << endl;
    fout << endl;

    // Parcourt toutes les faces et écrit leurs sommets.
    for(int i=0; i<faceIndex; i++)
    {
        vIndex = faces[i].vIndex1 - 1;
        tIndex = faces[i].tIndex1 - 1;
        nIndex = faces[i].nIndex1 - 1;

        fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
             << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
             << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;

        vIndex = faces[i].vIndex2 - 1;
        tIndex = faces[i].tIndex2 - 1;
        nIndex = faces[i].nIndex2 - 1;

        fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
             << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
             << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;

        vIndex = faces[i].vIndex3 - 1;
        tIndex = faces[i].tIndex3 - 1;
        nIndex = faces[i].nIndex3 - 1;

        fout << vertices[vIndex].x << ' ' << vertices[vIndex].y << ' ' << vertices[vIndex].z << ' '
             << texcoords[tIndex].x << ' ' << texcoords[tIndex].y << ' '
             << normals[nIndex].x << ' ' << normals[nIndex].y << ' ' << normals[nIndex].z << endl;
    }

    // Ferme le fichier de sortie.
    fout.close();

    // Libère la structure des quatre données.
    if(vertices)
    {
        delete [] vertices;
        vertices = 0;
    }
    if(texcoords)
    {
        delete [] texcoords;
        texcoords = 0;
    }
    if(normals)
    {
        delete [] normals;
        normals = 0;
    }
    if(faces)
    {
        delete [] faces;
        faces = 0;
    }

    return true;
}

    V. Résumé

    Dorénavant, nous pouvons convertir les fichiers Maya 2011 d'extension .obj vers notre format simple de modèle.

    Image non disponible

    VI. Exercices à faire

    1. Recompilez le programme et exécutez-le sur le fichier .obj fourni.
    2. Créez (ou demandez à quelqu'un) un modèle Maya 2011 et exportez-le au format .obj, puis exécutez ce programme pour le convertir.
    3. Modifiez ce code pour lire et exporter le format de modèle différent que vous préférez.

    VII. Code source

    • Projet Visual Studio 2010 : dx11tut08.zip.
    • Source seule : dx11src08.zip.
    • Exécutable seul : dx11exe08.zip.

    VIII. Remerciements

    Cet article est une traduction autorisée de l'article paru sur RasterTek.

    Merci à LittleWhite pour sa relecture lors de la traduction et jacques_jean pour sa relecture orthographique.

    Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

    Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
    Developpez.com

    Copyright © 2000-2015 - www.developpez.com


     Sommaire - DirectDraw - http://www.pageasm.com

            Initiation Intro.rapide  Les Dialogues Direct Draw Présentation

            Affichage simple
            Multiple
            Buffering
            Images GIFBlit
            Animation(1)
            Animation(2)
            Le bon choix

            Vous devez savoir programmer en assembleur sous Win32. N'oubliez pas aussi
            de télécharger les derniers fichiers de déclaration et unités si vous
            voulez lancer les exemples. Ce tutoriel nécessite DirectX 7.0.
            Présentation Affichage simple Multiple Buffering Images GIFBlit simple et
            avec transparence Animation simple Animation avancée Le bon choix
            Des données accompagnent ce Tutoriel:


Présentation - DirectDraw - http://www.pageasm.com

L'API de DirectX n'a pas un jeu fixe de fonction comme Win32, tout
simplement parcequ'elle évolue constament. Il existe seulement une fonction
initiale qui permet d'obtenir un pointeur vers un ensemble de fonctions.

Par exemple pour DirectDraw c'est DirectDrawCreateEx:

invoke DirectDrawCreateEx,NULL,addr pDD,addr IID_DIRECTDRAW7,NULL

Ce code permet d'obtenir  un pointeur pDD vers toutes les fonctions de DirectDraw.(si DirectX 7 ou supérieur est installé)

Malheureusement on ne peux plus utiliser invoke pour appeler ces fonctions, il faut les appeler soi-même, c'est à dire empiler
tous les arguments du dernier au premier,puis empiler la valeur du pointeur et enfin appeler via celui-ci.

Un exemple:push 0

push    0
push    16
push    480
push    640
mov     eax, pDD
push    eax
mov     eax, [eax]
call    dword ptr [eax+DD7_SETDISPLAYMODE]

Ici on appelle SetDisplayMode via le pointeur pDD qu'on a obtenu avec DirectDrawCreateEx.
Etant donné que ceci est très lourd, j'ai créé des macros qui permettent
d'appeler les fonctions un peu comme invoke:

DD7_SetDisplayMode pDD,640,480,16,0,0

Ce code est équivalent au précédent, à la différence que c'est beaucoup plus lisible.
Voici la macro:DD7_SetDisplayMode macro   pt, arg1, arg2, arg3, arg4, arg5

        mov     eax, arg5
        push    eax
        mov     eax, arg4
        push    eax
        mov     eax, arg3
        push    eax
        mov     eax, arg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_SETDISPLAYMODE]
        endm

Il peut parraître très lourd de tout empiler via eax, mais étant donné
que l'on ne connait pas la nature des arguments, on est obligé de faire
ainsi.Il y a aussi un autre problème, c'est lorsque l'on veut empiler
l'adresse d'une variable.

Avec invoke, on utilise addr, mais ici ce n'est pas possible.
C'est la raison pour laquelle, lorsqu'une adresse sur une variable est
attendue, ceci sera sous-entendu dans la macro. Il faudra donc faire
attention. Un exemple:DD7_CreateSurface macro   pt, lparg1, lparg2, arg3

        mov     eax, arg3
        push    eax
        lea     eax, lparg2
        push    eax
        lea     eax, lparg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_CREATESURFACE]
        endm

Ceci est incorrect:
DD7_CreateSurface pDD,addr ddsd,addr pDDSPrimary,NULL; incorrect !

Ceci est correct:
DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL; correct

J'insiste donc, faites attention, regardez bien les déclarations
des macros pour savoir si une variable ou une adresse de variable est
attendue (ie xxx = variable; lpxxx = adresse sur variable).
Sachez aussi que je n'ai créé que les macros pour les fonctions utilisées
par ce Tutoriel, donc il faudra soit télécharger la dernière version des
fichiers de déclaration, soit compléter la liste vous-même.


Structure du programme

Voici la structure du programme "simple.asm" qui illustre cette partie:
initialisation du programme création de la classe et de la fenêtre
initialisation de DirectDraw et du mode vidéocréation de la surface
primaire dessin boucle d'attente finalisation du programme

Création de la fenêtre
~~~~~~~~~~~~~~~~~~~~~~
Notre objectif est d'obtenir tout pouvoir sur l'écran, mais il faudra quand
même passer par les fenêtres de windows, avec son jeu habituel: une classe,
une procédure et une boucle d'attente. On commence donc par créer une
fenêtre qui prend tout l'écran, complètement dénudé (pas de barre de titre
et cie) et au plus haut niveau de priorité possible:

mov     wc.cbSize, sizeof WNDCLASSEX
mov     wc.style, CS_HREDRAW or CS_VREDRAW
mov     wc.lpfnWndProc, offset WindowProc
mov     wc.cbClsExtra, NULL
mov     wc.cbWndExtra, NULL
push    hInstance
pop     wc.hInstance
mov     wc.hbrBackground, COLOR_WINDOW + 1
mov     wc.lpszMenuName, NULL
mov     wc.lpszClassName, offset ClassName
invoke  LoadIcon,NULL,IDI_APPLICATION
mov     wc.hIcon, eax
mov     wc.hIconSm, NULL
invoke  LoadCursor,NULL,IDC_ARROW
mov     wc.hCursor, eax
invoke  RegisterClassEx,addr wc

invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName, addr AppName,WS_POPUP,0,0,ScreenX,ScreenY,NULL,NULL,hInstance,NULL

.if !eax
         invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
         invoke  ExitProcess,-1
.endif

mov     hWindow, eax
invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
invoke  UpdateWindow,hWindow
invoke  SetFocus,hWindow

ScreenX et ScreenY sont deux variables contenant les dimensions du bureau,obtenues par GetSystemMetrics.

La procédure de la fenêtre est très simple:

WindowProc      proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

WM_DESTROY:
 La fenêtre est détruite: on libère tous les objets DirectDraw (LibereObjets, cf simple.asm).

WM_KEYDOWN:

 On presse une touche: si cette touche est ,on envoie un message pour quitter.

WM_SETCURSOR: Choisir le curseur:
 on ne veut pas de curseur, donc on appelle SetCursor avec l'argument NULL.
 La boucle d'attente est elle aussi très simple:

.while  TRUE
        invoke  GetMessage,addr msg,NULL,0,0
        .break .if !eax
        invoke  TranslateMessage,addr msg
        invoke  DispatchMessage,addr msg
.endw

invoke  ExitProcess,msg.wParam

---------

        Initialisation du mode vidéoCommençons par initialiser DirectDraw:

invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL

.if eax != DD_OK
        invoke  Erreur,hWindow,addr eDirectDrawCreate
.endif

Remarquez la fonction Erreur:

- C'est une procédure contenu dans le fichier "simple.asm" qui affiche un message d'erreur et termine l'application.
  Remarquez aussi que toutes les fonctions DirectDraw renverrons dans eax le code d'erreur et le cas échéant DD_OK.

- Il faut ensuite préciser à DirectDraw le niveau de priorité de notre application: ici nous voulons tous les droits
  sur du plein écran, c'est à dire DDSCL_EXCLUSIVE et DDSCL_FULLSCREEN:

  DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN

.if eax != DD_OK
        invoke  Erreur,hWindow,addr eSetCooperativeLevel
.endif

Pour choisir le mode vidéo, rien de plus simple:

 DD7_SetDisplayMode pDD,640,480,16,0,0

.if eax != DD_OK
        invoke  Erreur,hWindow,addr eSetDisplayMode
.endif

          Ici nous demandons du 640 par 480 en 16-bit, c'est à dire 65536 couleurs.
          C'est un mode très pratique car il n'y a pas de palette, donc un accès à toutes les teintes que l'on veut et
          à tout moment, et il ne prends pas trop de place en mémoire.
          Reste enfin la création de la surface primaire qui nous permettera d'accéder directement à l'écran.

          Les surfaces de DirectDraw sont tout simplement des images avec un lot d'informations
              (dimensions, couleurs,effets, ... il y en a énormement).
          La surface primaire se distingue des autres car elle est la seule à avoir directement accès à l'écran.
          Toute modification de la surface primaire entraîne immédiatement un changement à l'écran.
          De plus elle est obligatoirement du format du mode vidéo, jusque dans ses moindres détails,
          comme sont Pitch (largeur réelle de l'écran en pixel), mais nous verrons cela plus tard:

          invoke  Clear,addr ddsd,size ddsd
          mov     ddsd.dwSize, size ddsd
          mov     ddsd.dwFlags, DDSD_CAPS
          mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE

DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
.if eax != DD_OK
        invoke  Erreur,hWindow,addr eCreateSurface
.endif

          ddsd est une structure DDSURFACEDESC2 qui contient un ensemble d'informations sur une surface.
          Ici nous voulons une surface primaire, chose qu'il faut  préciser dans la structure ddsCaps contenue
          dans ddsd avec le paramètre  DDSCAPS_PRIMARYSURFACE dans dwCaps.
          On obtient ainsi le pointeur pDDSPrimary qui possède, au même titre que pDD, un jeu de fonction.
          A la différence que cette fois-ci c'est un jeu de fonction pour surface DirectDraw.Dessin
          Pour dessiner nous allons utiliser uniquement les services de Windows.
          On peut aussi dessiner soit-même, mais c'est plus compliqué étant donné puisqu'il faut tenir compte
          des subtilités du mode. Donc pour ceci aussi nous verrons plus tard, voyons simple pour l'instant.

          Le but du dessin est d'avoir un cran noir avec un message au centre.La première chose à faire est donc
          d'effacer l'écran: on utilise pour cela la fonction Blt de pDDSPrimary, qui à la base sert à déplacer
          un sprite d'une surface à une autre, mais on peut aussi l'utiliser pour remplir une surface:

          invoke   Clear,addr ddbltfx,size ddbltfx
           mov     ddbltfx.dwSize, size ddbltfx
           mov     ddbltfx.dwFillColor, 0
          DDS7_Blt pDDSPrimary,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,ddbltfx

          ddbltfx est une structure DDBLTFX qui contient un effet et tous ses paramètres.
          Ici nous voulons remplir la surface, on le précise donc avec DDBLT_COLORFILL.
          Blt regarde alors dans ddbltfx la valeur de dwFillColor et rempli ainsi la surface avec celle-ci.
          DDBLT_WAIT indique seulement que Blt doit attendre avant de redonner la main ;
          cela est utile si on a l'intention de manipuler la surface juste après.
          Ensuite pour dessiner du texte, on va faire comme pour une fenêtre windows.
          En effet il est possible d'obtenir un handle de contexte (hDC), objet necessaire à l'utilisation
          de toute fonction de dessin pour fenêtre windows.
          C'est la fonction GetDC qui effectue cette opération:

        DDS7_GetDC pDDSPrimary,hDC

.if eax == DD_OK

        invoke  SetBkColor,hDC,00000FFh
        invoke  SetTextColor,hDC,0FFFF00h

        invoke  GetClientRect,hWnd,addr rect
        invoke  DrawText,hDC,addr Essai,-1,addr rect,DT_SINGLELINE or DT_CENTER or DT_VCENTER

        DDS7_ReleaseDC pDDSPrimary,hDC
.endif

- Ensuite pour dessiner le message à l'écran, comme vous pouvez le constater,
   c'est comme pour une fenêtre windows, la totalité de la surface étant considérée zone client.
   Vous pouvez vous amuser à faire appel à toutes les fonctions de dessin des fenêtre windows,
   mais je ne vous garantie pas les performances. Par contre au niveau de compatibilité,
   ça peut être assez pratique dans certain cas, étant donné que quelque soit le mode vidéo,
   vous avez la garantie d'un affichage correct... mais lent (très lent).

Terminer
~~~~~~~~
 Pour la destruction de la fenêtre c'est comme d'habitude.
 Il ne faut pas oublier de détruire les objets DirectDraw.
 Ceci est simple car tous les pointeurs sur fonction de DirectX on une même fonction en commun:
 Release qui permet de libérer l'objet.
 Voyez plutôt la fonction "LibereObjets" du programme:

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp


Multiple Buffering

Qu'est-ce que le Multiple Buffering ?

Le Multiple Buffering consiste à créer plusieurs buffer réservés à l'écran.
Le but est de ne pas avoir à gérer le fait que certains objet d'une image sont dessiné au dessus d'autres.
Par exemple imaginez que vous avez une image de fond qui prend tout l'écran, et que vous voulez placer
un sprite se déplaçant sur ce fond.
Si on dessine l'image directement à l'écran on verra toutes les étapes:
d'abord apparaîtra une image de fond, puis un sprite par dessus, puis de
nouveau une image de fond, etc...
Et ainsi de suite, le tout extrêment vite:  résultat le sprite clignote.

Le but premier du Buffering est de supprimer cet effet.
Le premier mode est le Simple Buffering qui consiste à créer un buffer en
mémoire (vidéo ou physique) où sera dessiné l'image.
Ensuite une fois l'image dessinée, le buffer est copié dans le buffer de
l'écran. Par exemple avec DirectDraw on dessine dans une surface en mémoire
puis on la copie dans la surface primaire. Ainsi seule l'image finale
apparaîtra à l'écran, supprimant les effets de cligotements. Le seul défaut
de cette méthode est qu'elle va ralentir la vitesse du rendu,tout simplement
parce qu'il faut accorder du temps au processeur (graphique ou central) pour
la copie.
C'est la raison pour laquelle on a pensé au Double Buffering qui consiste à
utiliser deux surfaces qui pourront être un coup primaire un coup non-primaire.
On commence par dessiner dans une première surface non-primaire,
puis on indique à la carte graphique que cette surface est primaire:

la carte graphique trace donc directement l'image sur l'écran.
Ensuite on dessine dans la deuxième surface non-primaire, puis on indique à
la carte graphique d'échanger le status de surface primaire entre la première
et la deuxième surface (c'est ce qu'on appelle un "flip"):
résultat la carte graphique affiche la deuxième surface.
La seule différence entre le Simple et le Double Buffering, c'est que le
Double Buffering supprime le temps consacré à la copie d'une surface dans
une autre. Lors du Double Buffering le temps nécessaire à l'affichage d'une
image est au pire celui dont a besoin votre carte graphique pour tracer
l'image sur votre écran (c'est le "temps nécessaire au flip").
Pour définitivement annihiler le temps de rafraîchissement, il existe le
Triple Buffering qui consiste à utiliser 3 surfaces. Le principe est le même
que pour le Double Buffering, à la différence que lorsqu'un flip est effectué
entre deux surfaces, la troisième est en train d'être dessinée.
Le seul problème du Triple Buffering est qu'il commence à prendre beaucoup
de mémoire vidéo, donc impossible avec certaines cartes graphiques.
Double BufferingLe programme d'exemple est "flip.asm", il a pour but
d'afficher un coup le message "Première Surface" un coup le message
"Deuxième Surface".DirectDraw permet de faire très facilement du double
buffering: il suffit de créer normalement sa surface primaire, de demander
une autre surface attachée à cette dernière (que l'on appellera la surface
de fond ou "back buffer"). Ensuite on appelle une fonction qui flip entre
les deux surfaces. C'est tellement bien fait qu'on a même pas besoin
d'échanger les surfaces: aux yeux du programmeur, c'est toujours la deuxième
surface qui est la surface de fond.Pour le programme d'exemple, c'est en fait
presque le même que le précédent: la fenêtre, sa boucle d'attente,
l'initialisation de DirectDraw, tout ceci reste inchangé.La création de la
surface primaire est toutefois un peu différente: il faut en effet préciser
à DirectDraw que nous voulons une surface de fond et une fonction de flip:

invoke  Clear,addr ddsd,size ddsd
mov     ddsd.dwSize, size ddsd
mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or \
         DDSCAPS_FLIP or DDSCAPS_COMPLEX
mov     ddsd.dwBackBufferCount, 1
DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
.if eax != DD_OK
        invoke  Erreur,hWindow,addr eCreateSurface
.endif

Pour créer notre surface de fond, il suffit de le demander à DirectDraw à
partir de la surface primaire:

invoke  Clear,addr ddscaps,size ddscaps
mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
.if eax != DD_OK
        invoke  Erreur,hWindow,addr eGetAttachedSurface
.endifpDDS

Back est donc notre surface de fond.Pour effectuer notre animation
(échanger deux messages), nous allons utiliser le timer qui va appeller à
intervalles régulier une fonction qui va dessiner dans la surface de fond
puis la "flipper" avec la surface primaire.On créé donc l'appel au timer:

invoke  SetTimer,hWindow,1,500,NULL

.if eax != 1
        invoke  Erreur,hWindow,addr eTimer
.endif

La procédure WndProc de la fenêtre principale se voit augmenter de la gestion
 du message WM_TIMER:.elseif eax == WM_TIMER
        .if wParam == 1 && bActive
             invoke  UpdateFrame,hWnd
             .while TRUE
                    DDS7_Flip pDDSPrimary,NULL,0
                    .if eax == DD_OK
                            .break
                    .elseif eax == DDERR_SURFACELOST
                            DDS7_Restore pDDSPrimary
                            .break .if eax != DD_OK
                    .elseif eax == DDERR_WASSTILLDRAWING
                            .break
                    .endif
             .endw
        .endif Update

Frame est une fonction du programme que nous verrons plus loin.
C'est elle qui affiche les messages dans la surface de fond.
La boucle qui suit a pour but d'attendre que le flip soit bel et bien demarré.
 Si la fonction DDS7_Flip renvoie DD_OK, c'est que le flip est terminé.
 Si c'est DDERR_SURFACELOST, c'est que le flip n'a pas pu démarrer car la
 surface a été perdu (cela arrive lorsque le système se bloque pendant quelques
  instant ou que l'on presse Alt+Tab). On restaure donc la surface avec
  DDS7_Restore, puis on retente un flip. Enfin si c'est DDERR_WASSTILLDRAWING,
  c'est que le flip n'a pas pu être effectué, mais qu'il le sera bientôt.
  On peut donc quitter la boucle et continuer. Notez que c'est lorsqu'on reçoit
  DDERR_WASSTILLDRAWING que le Double Buffering devient intéressant: on peut
  continuer l'application alors que le flip n'est pas encore fini.
  Le seul problème est que lorsqu'on voudra redessiner dans la surface de fond,
  si le flip n'est toujours pas fini, il faudra quand même attendre.
  Voyons maintenant la fonction UpdateFrame:

  UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT
 invoke  Clear,addr ddbltfx,size ddbltfx
 mov     ddbltfx.dwSize, size ddbltfx
 mov     ddbltfx.dwFillColor, 0
 DDS7_Blt pDDSBack,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,\
           ddbltfx

 DS7_GetDC pDDSBack,hDC
 .if eax == DD_OK
     invoke  SetBkColor,hDC,00000FFh
     invoke  SetTextColor,hDC,0FFFF00h
     .if phase
         mov     phase, 0
         invoke  StrLen,addr Front
         invoke  TextOut,hDC,0,0,addr Front,eax

         invoke  GetClientRect,hWnd,addr rect
         invoke  DrawText,hDC,addr Essai,-1,addr rect,\
                  DT_SINGLELINE or DT_CENTER or DT_VCENTER
     .else
         mov     phase, 1
         invoke  StrLen,addr Back
         invoke  TextOut,hDC,0,0,addr Back,eax
     .endif
     DDS7_ReleaseDC pDDSBack,hDC
 .endif

 ret
UpdateFrame     endp

Détaillons un peu cette fonction:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On commence par remplir de noir la surface de fond:
l'opération est la même que dans "simple.asm":

invoke  Clear,addr ddbltfx,size ddbltfx
 mov     ddbltfx.dwSize, size ddbltfx
 mov     ddbltfx.dwFillColor, 0
 DDS7_Blt pDDSBack,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,\
           ddbltfx

Puis on obtient le handle de contexte et met à jour les couleurs du texte:
 DS7_GetDC pDDSBack,hDC

 .if eax == DD_OK
     invoke  SetBkColor,hDC,00000FFh
     invoke  SetTextColor,hDC,0FFFF00h

Ensuite en fonction de l'image courante (phase 0 ou phase 1) on dessine le
message correspondant:

.if phase
         mov     phase, 0
         invoke  StrLen,addr Front
         invoke  TextOut,hDC,0,0,addr Front,eax

         invoke  GetClientRect,hWnd,addr rect
         invoke  DrawText,hDC,addr Essai,-1,addr rect,\
                  DT_SINGLELINE or DT_CENTER or DT_VCENTER
.else
         mov     phase, 1
         invoke  StrLen,addr Back
         invoke  TextOut,hDC,0,0,addr Back,eax
.endif

Et enfin on libère le handle de contexte:

   DDS7_ReleaseDC pDDSBack,hDC
.endif

Triple Buffering
~~~~~~~~~~~~~~~~
Pour le Triple Buffering, il n'y a pas grand chose à faire.
Il suffit de demander deux surface de fond à DirectDraw.
Souvenez vous de la création de la surface primaire:

invoke  Clear,addr ddsd,size ddsd
mov     ddsd.dwSize, size ddsd
mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or \
         DDSCAPS_FLIP or DDSCAPS_COMPLEX
mov     ddsd.dwBackBufferCount, 1
DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
.if eax != DD_OK
        invoke  Erreur,hWindow,addr eCreateSurface
.endif

Et bien pour passer au Triple Buffering, vous avez juste à changer cette ligne:
 mov     ddsd.dwBackBufferCount, 1
Par celle-ci:
 mov     ddsd.dwBackBufferCount, 2

Et c'est tout !  DirectDraw s'occupe de tout, pDDSBack représentera toujours
la surface de fond libre et DDS7_Flip "flippera" entre l'autre surface de
fond et la surface primaire. Vous pouriez même faire du Quadruple Buffering
avec 3 surfaces de fond, mais cela n'aurait aucun n'interêt.Ainsi pour le
programme "flip.asm", si vous voulez voir le même programmer tourner en
Triple Buffering, il suffit de faire le petit changement. Tous les autres
programmes des sections suivantes de ce Tutoriel seront en Double Buffering,
pour être sûr que votre carte graphique les supporteront. Vous pouvez bien
sûr tous les voir en Triple Buffering mais étant donné que l'animation est
limité par le Timer, vous ne verrez aucune différence,sauf pour les exemples
des dernières sections. Notez aussi que le Triple Buffering n'a d'intérêt
que si vous dépassez les 60 images par secondes.
Le programme "triple.asm" dessine une fois pour toutes au début les trois
surfaces, puis les affiche successivement. Ceci vous permettera de voir si
votre carte graphique supporte bien le Triple Buffering.

      http://www.pageasm.com
          Ce matériel ne doit pas être publié, revendu ou redistribué sans
          l'autorisation de Philippe-Henri Gosselin.


Images GIF

Le Format GIF Charger l'image dans une surface Les détails du chargement
Le Format GIF permet, comme vous devez très bien le savoir, de stoquer des
images sur le disque avec une compression.
Les avantages et les inconvénients du format dépendent du support de stockage.
Dans tous les cas,le plus gros avantage est bien sûr le gain en espace disque.
Par contre il faut tenir compte du temps nécessaire au décodage.
En effet, la méthode utilisée par les fichiers GIF pour compresser est celle
de LZW qui n'est pas une des plus rapides. Par exemple, si le fichier est
chargé depuis un disque dur, le chargement sera plus lent que celui d'un
fichier non compressé (comme les BMP), étant donné que la différence entre
le temps de chargement d'un fichier et un autre 4 ou 5 fois plus gros
(c'est en général le taux de compression) est négligeable sur un disque dur.
Par contre si vous chargez vos images depuis un CD-ROM, le format GIF devient
alors intéressant au niveau de la vitesse de chargement.
Le format GIF peut aussi devenir très interressant si vous voulez stoquer des
animations, mais ce n'est pas le sujet de ce Tutoriel. Si vous voulez en
savoir plus sur le Format GIF, allez voir dans la section Documents,
Le Format GIF.

Charger l'image dans une surface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pour nos exemples, notre but est de charger des images GIF en 256 couleurs et
ensuite de les convertir en 65535 couleurs. C'est justement le travail de la
procédure "ChargerGIF" contenue dans l'unité "graph.inc".
Assurez-vous donc que vous avez téléchargé les unités pour pouvoir compiler
les exemples qui vous suivrent. Cette procédure est détaillée dans la section
suivante.L'exemple de cette partie est "gif.asm". C'est exactement le même
programme que "flip.asm", à la différence que l'on a remplacé le fond noir
par une image. Le chargement de l'image se situe juste après la création de
la surface primaire. Il faut commencer par créer une surface où l'on pourra
stocker notre image:

invoke  Clear,addr ddsd,size ddsd

mov     ddsd.dwSize, size ddsd
mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
mov     ddsd.dwWidth, 640
mov     ddsd.dwHeight, 480
DD7_CreateSurface pDD,ddsd,pDDS1,NULL
.if eax != DD_OK
        invoke  Erreur,hWindow,addr eCreateSurface
.endif

Contrairement à la surface primaire, ici il faut préciser les dimensions de
la surface: tout d'abord on l'indique avec DDSD_HEIGHT et DDSD_WIDTH, puis
on rempli convenablement dwWidth et dwHeight (ici notre image est en 640 par
480). Il faut aussi préciser le fait que nous voulons une surface libre,
c'est à dire qu'elle n'est pas lié à l'écran comme la surface de fond et la
surface primaire: on l'indique dans les Caps avec DDSCAPS_OFFSCREENPLAIN.
Enfin la création de la surface se fait aussi avec DD7_CreateSurface, et on
obtient un pointeur pDDS1 vers un ensemble de fonctions de surface.
On appelera cette surface "surface 1". Le chargement et la conversion sont
entièrement effectués par "ChargerGIF" si le fichier contient bien une unique
image non interlacée:

invoke  ChargerGIF,addr sFichierGIF,640,480,pDDS1,addr eErreur

.if !eax
        invoke  Erreur,hWindow,addr eErreur
.endif

Les arguments de la procédure sont:

- le nom du fichier (ici sFichierGIF).
- les dimensions de l'image (ici 640 et 480).
  Si l'image n'a pas ces dimensions, la procédure renvoie une erreur.
  Ceci évite les bugs si on tente sans le savoir de charger une image trop grande.
- la surface où doit être placée l'image (ici pDDS1).
- une chaine de caractère ou sera placé le message d'erreur.

Ensuite dans la procédure "UpdateFrame" on remplace le remplissage uniforme
de la surface de fond par la copie de la surface 1:

invoke  GetClientRect,hWnd,addr rect

DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

La fonction DDS7_BltFast est une version simplifié de DDS7_Blt, mais
optimisée. Enfin, dans la documentation de Microsoft, il paraît qu'elle est
plus rapide. Personnellement je n'ai vu aucune différence, si vous avez une
carte accélératrice, c'est aussi rapide, sinon c'est ultra-lent.
Le seul avantage est qu'elle est plus simple à manipuler.
Les arguments de la fonction sont:

- la surface destination (ici pDDSBack).
- les coordonnées de destination (ici 0,0).
- la surface source (ici pDDS1).
- la zone de dessin (ici la variable "rect" obtenue avec GetClientRect
  représente tout l'écran).
- les options (ici DDBLTFAST_NOCOLORKEY).

Rappel sur les fonctions de DirectX:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attention, je considère DDS7_BltFast comme étant une fonction normale, alors
que c'est en fait une macro qui appelle la fonction DDS7_BLTFAST via pDDSBack.
pDDSBack n'est donc pas vraiment un argument.Les détails du chargementJe vais
détailler dans cette partie la fonction "ChargerGIF" de l'unité "graph.inc".
Normalement les unités sont installées dans "\masm32\units".
On va utiliser un ensemble de fonction contenues dans "gif.inc"
(une autre unité). Celles-ci par contre ne seront pas détaillées, mais elle
ne sont pas très intéressantes car elles se contentent de décoder les blocks
du fichier GIF.
On commence tout d'abord par ouvrir le fichier:

invoke  GIF_Ouvrir,lpFichier,addr hGIF

or      eax, eax
jz      erreur_giflp

Fichier est une chaîne de caractères avec le nom de notre fichier GIF, et
hGIF est une structure HGIF définie dans "units.inc" (encore une autre unité).
hGIF permet de dialoguer avec les fonctions de "gif.inc"
(regarder la structure HGIF dans "units.inc").
Par exemple ici elle indique la présence d'une palette globale dans le champs
"bPaletteGlobale". Dans le cas présent nous voulons charger une image simple
avec une palette globale, donc on quitte avec une erreur si ce n'est pas le
cas:

cmp     hGIF.bPaletteGlobale, TRUE
jne     erreur_format

Ensuite on peut décoder la palette globale:

invoke  GIF_DecoderPaletteGlobale,addr hGIF,addr dbPaletteTemp
or      eax, eax
jz      erreur_gif

"dbPaletteTemp" est un tableau de 768 octets contenant la palette du fichier.
Puis il faut déterminer la nature du premier block du fichier, c'est le
travail de GIF_InfoBlock:invoke  GIF_InfoBlock,addr hGIF

or      eax, eax
jz      erreur_gif

Il faut vérifier que ce block est une image,qu'elle n'a pas de palette locale,
qu'elle n'est pas interlacée, et enfin que ces dimensions sont correctes:

cmp     hGIF.dwTypeBlock, BGIF_IMAGE
jne     erreur_format
cmp     hGIF.bPaletteLocale, FALSE
jne     erreur_format
cmp     hGIF.bInterlace, FALSE
jne     erreur_format
mov     eax, dwLargeurImage
cmp     hGIF.dwLargeurImage, eax
jne     erreur_format
mov     eax, dwHauteurImage
cmp     hGIF.dwHauteurImage, eax
jne     erreur_format

On créé un buffer temporaire "lpImageTemp" de la taille de l'image pour
recevoir l'image décodé en 256 couleurs:

mov     eax, hGIF.dwLargeurImage
mul     hGIF.dwHauteurImage
invoke  GlobalAlloc,GMEM_FIXED,eax
or      eax, eax
jz      erreur_memoire
mov     lpImageTemp, eax

On décode l'image:

invoke  GIF_DecoderImage,addr hGIF,lpImageTemp
or      eax, eax
jz      erreur_gif

A présent lpImageTemp contient l'image décodée, mais en 256 couleurs.
Il faut faire une convertion si la surface de destination "lpdds7" n'est pas
en 256 couleurs. On va donc demander à DirectDraw le format de la surface de
destination avec DDS7_GetPixelFormat:

invoke  Clear,addr ddpixelformat,size DDPIXELFORMAT
mov     ddpixelformat.dwSize, size DDPIXELFORMAT
DDS7_GetPixelFormat lpdds7,ddpixelformat
cmp     eax, DD_OK
jne     erreur_surface

On place juste la taille de DDPIXELFORMAT dans ddpixelformat,
DDS7_GetPixelFormat n'a besoin d'aucune information pour remplir cette
structure.Il va falloir accéder directement la surface lpdds7:
on obtient un pointeur vers l'image qu'elle contient avec DDS7_Lock:

invoke  Clear,addr ddsd2,size DDSURFACEDESC2
mov     ddsd2.dwSize, size DDSURFACEDESC2
DDS7_Lock lpdds7,NULL,ddsd2,NULL,NULL
cmp     eax, DD_OK
jne     erreur_surface

A présent "ddsd2.lpSurface" contient l'adresse de l'image de destination.
Reste à faire la convertion:
au moment où ce Tutoriel a été écrit seuls le mode 16-bit (65536 couleurs)
était considéré. Mais attention, il existe plusieurs modes 16-bit.
Selon la documentation de microsoft, DirectDraw n'en supporte que deux:
l'un en 5-6-5 et l'autre en 5-5-5.
Pour connaître la répartition des bits il faut consulter les champs
dwXBitMask de ddpixelformat, avec X = R, G ou B. Voyons le code:

.if ddpixelformat.dwRGBBitCount == 16 \
 && ddpixelformat.dwRBitMask == 0F800h \
 && ddpixelformat.dwGBitMask == 007E0h \
 && ddpixelformat.dwBBitMask == 0001Fh
    invoke  Convert8to16,hGIF.dwLargeurImage,hGIF.dwHauteurImage, \
            lpImageTemp,addr dbPaletteTemp,ddsd2.lpSurface
.elseif ddpixelformat.dwRGBBitCount == 16 \
 && ddpixelformat.dwRBitMask == 07C00h \
 && ddpixelformat.dwGBitMask == 003E0h \
 && ddpixelformat.dwBBitMask == 0001Fh
    invoke  Convert8to16b,hGIF.dwLargeurImage,hGIF.dwHauteurImage, \
           lpImageTemp,addr dbPaletteTemp,ddsd2.lpSurface
.else
    DDS7_UnLock lpdds7,NULL
    invoke  Concat,lpFichier,addr eChargerGIF_Format,lpErreur
    jmp     erreur
.endifLes procédures Convert8to16 et Convert8to16b sont contenues dans "graph.inc".Le décodage est terminé, il ne reste plus qu'à "unlocker", libérer l'image temporaire et fermer le fichier:DDS7_UnLock lpdds7,NULL
cmp     eax, DD_OK
jne     erreur_surface

invoke  GlobalFree,lpImageTemp

invoke  GIF_Fermer,addr hGIF
mov     eax, TRUE
ret

Le code qui suit est l'ensemble des labels appelés lorsqu'il y a une erreur:

erreur_memoire:
    invoke  Concat,lpFichier,addr eChargerGIF_Memoire,lpErreur
    jmp     erreur
erreur_surface:
    invoke  Concat,lpFichier,addr eChargerGIF_Surface,lpErreur
    jmp     erreur
erreur_format:
    invoke  Concat,lpFichier,addr eChargerGIF_ErreurFormat,lpErreur
    jmp     erreur
erreur_gif:
    invoke  GIF_Erreur,addr hGIF,addr sBuf
    invoke  Concat,lpFichier,addr eChargerGIF_Erreur,lpErreur
    invoke  Append,lpErreur,addr sBuf
erreur:
    invoke  GIF_Fermer,addr hGIF
    xor     eax, eax
    ret

Toutes les erreurs sauf "erreur_gif" sont simplement des messages copiés dans
un buffer. "erreur_gif" appelle une autre fonction de "gif.inc" qui renvoie
la denière erreur des fonctions gif.

http://www.pageasm.com
Ce matériel ne doit pas être publié, revendu ou redistribué sans l'autorisation de Philippe-Henri Gosselin.


Blit

Principe du Blit , simple Blit avec transparence
~~~~~~~~~~~~~~~~~~
Le blit consiste à copier une partie d'une surface dans une autre
(avec ou sans effets), ce qui permet de créer des sprites et des animations.

Voyons plutôt sur un schémas:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ici nous utilisons 2 surfaces:
- la première contient l'arrière plan, elle est donc copiée intégralement
  dans la surface de fond.
- Dans la deuxième nous avons du texte sur fond noir "DirectDraw" qui est
  aussi copié dans la surface de fond.
La série de petits hélicoptères sera utilisée dans les parties suivantes.

Blit simple
~~~~~~~~~~~~
 Le blit simple copie un sprite tel quel, sans ancun effet.
 La surface de fond du schémas précédent est l'image que donne le programme
"blit1.asm".

"blit1.asm" est comme "gif.asm", à la différence que nous chargeons deux
 surfaces et que la procédure "UpdateFrame" est un peu changée:

UpdateFrame     proc    hWnd:HWND

LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT

 invoke  GetClientRect,hWnd,addr rect

 DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

 mov     rect.left, 0
 mov     rect.top, 0
 mov     rect.right, 510
 mov     rect.bottom, 75
 DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_NOCOLORKEY

 DDS7_GetDC pDDSBack,hDC
 .if eax == DD_OK
     invoke  SetBkColor,hDC,0F0F0F0h

 ... la suite est inchangée ...
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 La copie de la surface de fond est comme précédemment:

 invoke  GetClientRect,hWnd,addr rect

DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

Pour le blit du texte il faut préciser les coordonnées du sprite à copier
(0,0)x(510,75) ainsi que les coordonnées de destination (65,202):

mov     rect.left, 0
mov     rect.top, 0
mov     rect.right, 510
mov     rect.bottom, 75

DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_NOCOLORKEYBlit

avec transparenceLe programme d'exemple est "blit2.asm".
Le blit avec transparence est identique au blit simple, à la différence
qu'une couleur ne sera pas affichée: c'est ce que DirectDraw appelle la
"colorkey". L'avantage est que l'on peut copier des sprites non rectangulaires.
La colorkey est une des informations que contient une surface, et pour la
modifier il faut appeler DDS7_SetColorKey. Comme ici nous voulons copier le
texte "DirectDraw" qui est sur fond noir, on donne la répartition de rouge,
vert et bleu du noir à DDS7_SetColorKey pour la surface 2:

mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
mov     ddcolorkey.dwColorSpaceHighValue, 0000000h

DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
La répartition des couleurs sur un double mot est 000RRVVBBh, avec RR=rouge,
VV=vert et BB=bleu. ddcolorkey est une structure de type DDCOLORKEY.
DDCKEY_SRCBLT indique que nous voulons modifer la colorkey de la source
(il existe une colorkey de destination, mais je n'ai pas réussi à l'utiliser:
lorsque je demande de modifer cette colorkey, DDS7_SetColorKey me donne une
erreur!).Ensuite le blit est identique, à la différence qu'il faut préciser
la précense d'une colorkey en remplaçant DDBLTFAST_NOCOLORKEY par
DDBLTFAST_SRCCOLORKEY:

mov     rect.left, 0
mov     rect.top, 0
mov     rect.right, 510
mov     rect.bottom, 75
DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

http://www.pageasm.com
Ce matériel ne doit pas être publié, revendu ou redistribué sans l'autorisation de Philippe-Henri Gosselin.


Animation (1)

Pour animer un sprite, il suffit de créer une suite d'images.
Dans l'exemple de cette partie, "anim1.as", nous allons faire tourner les
hélices d'un hélicoptère.L'animation se fera à partir de l'image "ddraw2.gif":
La première chose à faire est de créer une table qui contiendra les
coordonnées de chaque image, ainsi on pourra accéder à n'importe laquelle:

tblHelicoX      dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
tblHelicoY      dd      77,77,77,77,77,77
                dd      137,137,137,137,137,137
                dd      197,197,197,197,197,197
                dd      257,257,257,257,257,257
                dd      317,317,317,317,317,317

Ensuite on ajoute dans "UpdateFrame" le blit de l'hélicoptère:
       inc     anim
.if anim  >= 30
        mov     anim, 0
.endif

mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
mov     ebx, anim
mov     eax, tblHelicoX[ebx*4]
mov     rect.left, eax
add     eax, 98
mov     rect.right, eax
mov     ebx, anim
mov     eax, tblHelicoY[ebx*4]
mov     rect.top, eax
add     eax, 58
mov     rect.bottom, eax
DDS7_BltFast pDDSBack,270,302,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

Le blit est fait comme pour celui du texte "DirectDraw", en fin de compte la
seule différence est que les coordonnées source du sprite change pour chaque
image.Note: l'hélicoptère est sur fond bleu, mais comme on est en 16-bit le
bleu tient sur les 5 premiers bits, or DDS7_SetColorkey vous le demande sur
8 bits: il faut donc annuler les 3 premiers bits et placer dans les 5 bits
au-dessus la quantité de bleu: 01Fh = 11111000b. N'oubliez pas aussi qu'il
existe deux modes de 16-bit: 5-6-5 et 5-5-5. Donc si vous êtes sous fond
rouge ou vert il faut faire la distinction. Je vous conseille donc de
toujours utiliser des fonds noir et bleu.RetournementOn va maintenant faire
évoluer notre hélicoptère sur le fond en le faisant se déplacer du bord
gauche au bord droit de l'écran, et vise-versa.Le premier problème est que
de gauche à droite, l'hélicoptère va regarder vers la gauche, ce qui n'est
pas très logique! On pourrait très bien re-créer une suite d'hélicoptères qui
regardent vers la droite, mais ce serait une perte inutile de mémoire.
En effet, DirectDraw vous permet d'afficher le sprite dans l'autre sens, et
ce sans perte de vitesse (si vous avez une carte accélératrice).
On ne peux pas faire le retournement avec BltFast, on va donc utiliser Blt.
Je rappelle que si vous avez une carte accélératrice, il n'y a aucune
différence de performance entre Blt et BltFast, c'est juste que BltFast est
plus facile à manipuler.Voyons les changements dans "anim2.asm".
Le choix de colorkey et les coordonnées sources du sprites sont inchangées:

mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
mov     ebx, anim
mov     eax, tblHelicoX[ebx*4]
mov     rect.left, eax
add     eax, 98
mov     rect.right, eax
mov     ebx, anim
mov     eax, tblHelicoY[ebx*4]
mov     rect.top, eax
add     eax, 58
mov     rect.bottom, eax

Ensuite il faut pour Blt créer une autre structure RECT qui contiendra les
coordonnées de destination du sprite. Notez que si celles-ci représentent un
sprite plus grand, celui-ci sera agrandi et dans certains cas même lissé.
Même chose si la zone est plus petite.

mov     rectdest.left, 270
mov     rectdest.top, 302
mov     rectdest.right, 270+98
mov     rectdest.bottom, 302+58

La fonction Blt nécéssite une structure DDBLTFX qui indiquera que l'image doit être retournée (DDBLTFX_MIRRORLEFTRIGHT):

invoke  Clear,addr ddbltfx,size ddbltfx
mov     ddbltfx.dwSize, size ddbltfx
mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or DDBLT_KEYSRC or DDBLT_DDFX,ddbltfxDDBLT_WAIT

indique que la fonction doît redonner la main lorsque le Blit est terminé
(nécessaire si vous voulez accéder immédiatement après aux surfaces).
DDBLT_KEYSRC indique qu'il y a une colorkey source.
DDBLT_DDFX indique qu'il y a un effet.
Notez qu'il existe d'autres effets disponibles:
DDBLTFX_MIRRORLEFTRIGHT:
retournement selon l'axe des y.DDBLTFX_MIRRORUPDOWN:
retrournement selon l'axe des x.DDBLTFX_ROTATE90:
rotation de 90°.DDBLTFX_ROTATE180:
rotation de 180°.DDBLTFX_ROTATE270:
rotation de 270°.Mouvement

Le programme "anim3.asm" illustre cette partie.Comme expliqué précédemment,
notre but est de faire évoluer l'hélicoptère de gauche à droite puis de
droite à gauche. On va donc commencer par créer une variable HelicoX qui va
contenir la position actuelle de l'hélicoptère, et une variable HelicoSens
qui va contenir la vitesse (valeur absolue) et la direction (signe) de
l'hélicoptère.Première chose, on fait évoluer des coordonnées de l'hélicoptère:

mov     eax, HelicoSens
add     HelicoX, eax
cmp     HelicoX, 0
jge     @F
neg     HelicoSens
mov     HelicoX, 0
@@:
cmp     HelicoX, 540
jle     @F
neg     HelicoSens
mov     HelicoX, 540
@@:Explications:
on ajoute HelicoSens à HelicoX, comme c'est un entier relatif (il peut être
négatif ou positif), ceci va augment ou diminuer la valeur de HelicoX.
Ensuite on regarde si HelicoX est plus grand que zéro, afin de s'assurer que
l'hélicoptère ne va pas sortir de l'écran. Si c'est le cas, on continue.
Par contre si ce ne l'est pas, on inverse HelicoSens, ce qui va faire
progresser l'hélicoptère dans l'autre sens, et on donne la valeur 0 à
HelicoX au cas où HelicoX serait négatif. Ensuite pour le bord droit de
l'écran, on fait la même chose sauf qu'on vérifie que HelicoX est plus petit
que 540.La colorkey et les coordonnées source sont les mêmes, par contre les
coordonnées destination sont en fonction de HelicoX:

mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
mov     ebx, anim
mov     eax, tblHelicoX[ebx*4]
mov     rect.left, eax
add     eax, 98
mov     rect.right, eax
mov     ebx, anim
mov     eax, tblHelicoY[ebx*4]
mov     rect.top, eax
add     eax, 58
mov     rect.bottom, eax

mov     eax, HelicoX
mov     rectdest.left, eax
add     eax, 98
mov     rectdest.right, eax
mov     rectdest.top, 302
mov     rectdest.bottom, 302+58

Le blit est le même à la différence que l'on va inverser l'hélicoptère
uniquement s'il va de gauche à droite, c'est à dire HelicoSens positif:

invoke  Clear,addr ddbltfx,size ddbltfx

mov     ddbltfx.dwSize, size ddbltfx
cmp     HelicoSens, 0
jle     @F
mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
@@:
DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or \
         DDBLT_KEYSRC or DDBLT_DDFX,ddbltfx

http://www.pageasm.com
Ce matériel ne doit pas être publié, revendu ou redistribué sans l'autorisation de Philippe-Henri Gosselin.


Animation (2)

Se débarrasser du Timer
Exemples de techniques d'animation
Se débarrasser du Timer
Lors des exemples précédents, nous avons toujours utilisé le timer pour savoir quant il fallait rafraîchir notre écran.
Mais l'inconvénient de cette méthode est qu'on est limité à la fréquence du timer, qui a souvent du mal à dépasser les 20 Hz...
On est loin des 60 Hz qui est la fréquence idéale pour toute animation.

Pour atteindre ceci, on va tout simplement rafraîchir l'écran en permanence:
à peine une image est en cours de flip qu'on va commencer le rendu de la
suivante. Ceci permet d'atteindre la fréquence la plus élevé possible et
sans aucune limite sinon le taux de rafraîchissement de votre écran.
Notez aussi que c'est là que le Triple Buffering devient vraiment intéressant.

Pour rafraîchir en permanence, on va tout simplement transformer la boucle d'attente de la fenêtre de notre application.
Au lieu d'attendre un message, elle va regarder s'il y a un message:
si c'est le cas elle le traite, si ce n'est pas le cas elle rafraîchit l'écran:

.while  TRUE
        invoke  PeekMessage,addr msg,NULL,0,0,PM_NOREMOVE
        .if eax
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .elseif bActive
                invoke  UpdateFrame,hWindow
        .else
                invoke  WaitMessage
        .endif
.endwPeekMessage,

à la différence avec GetMessage, regarde s'il y a un message et renvoie
dans eax cette information. PM_NOREMOVE signifie que le message lu
(s'il y en a un) ne doît pas être retiré de la pile. Si un message est
présent (eax égal à 1) on traite le message comme d'habitude.

Par contre s'il n'y a pas de message, on rafraîchit l'écran, mais uniquement si l'application est active:
si on presse Alt+Tab et qu'on revient sur le bureau de windows, ce serait une véritable catastrophe
si on continuait de monopoliser le processeur!.  Le programme d'exemple "anim4.asm" illustre cette partie.

On a juste modifié la boucle d'attente, supprimé le timer et ajouté en
haut à gauche de l'écran le nombre d'images par seconde. Pour se faire,
on utilise le TickCount, un compteur indépendant du processeur.
En appelant la fonction "GetTickCount", on obtient le temps
passé depuis le lancement de windows en millisecondes, ce qui permet de calculer le nombre d'images par seconde:

        inc     ips
invoke  GetTickCount
sub     eax, 1000
.if eax > TickCountRate
        add     eax, 1000
        mov     TickCountRate, eax
        mov     eax, ips
        mov     lastips, eax
        mov     ips, 0
.endif

La méthode est la suivante:
on mémorise une première fois le TickCount dans la variable "TickCountRate".
A chaque rafraîchissement on incrémente la variable "ips" jusqu'à ce que le
temps écoulé depuis la prise de TickCountRate soit de 1 seconde
(1000 millisecondes). Lorsque ceci est atteint, ips contient le nombre
d'images par seconde actuel. Il ne reste plus qu'à remettre le TickCount
actuel dans TickCountRate et annuler ips.Exemples de techniques d'animation
La méthode présentée précédemment est intéressante au niveau du taux de
rafraîchissement, mais il devient impossible de contrôler la vitesse des
animations! Si le taux est de 20 Hz l'application sera extrêmment lente,
et si le taux est de 100 Hz se sera trop rapide!Les techniques d'animation
que nous allons voir permettent de résoudre ce problème en adaptant la
vitesse des animations en fonction du taux de rafraîchissement.
Par exemple, un sprite mettra toujours le même temps pour parcourir l'écran
de gauche à droite, la seule différence sera que dans certains cas se sera
saccadé et dans d'autre extrêmment fluide.
Notez que c'est ce genre de techniques qui sont toujours utilisées en 3D,
et que ce sont aussi celles qui donnent les plus belles animations.

Mais attention ! Il ne faut pas vous jeter dessus ! Les sprites ne sont pas aussi facilement contrôlable que dans la méthode "Timer":
il va falloir utiliser les réels, les sprites ne seront jamais alignés sur les pixels. Si la méthode vous semble trop lourde pour ce
que vous voulez faire, je vous conseille d'utiliser les même techniques d'animation que pour la méthode "Timer",
mais avec un rafraîchissement limité. C'est à dire que l'on empêche le rafraîchissement d'être plus grand qu'une
certaine valeur, en misant sur le fait que votre application ne seras jamais trop gourmande en calculs.
Voici un exemple de boucle d'attente qui effectue cette opération:

.while  TRUE
        invoke  PeekMessage,addr msg,NULL,0,0,PM_NOREMOVE
        .if eax
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .elseif bActive
                mov     eax, CurrentTickCount
                mov     LastTickCount, eax
                invoke  GetTickCount
                mov     ebx, eax
                sub     eax, LastTickCount
                .if eax >= 20
                        mov     CurrentTickCount, ebx
                        invoke  UpdateFrame,hWindow
                .endif
        .else
                invoke  WaitMessage
        .endif
.endw

Dans l'exemple ci-dessus on limite le rafraîchissement à 50 Hz
(20 x 50 = 1000 ms).Voyons maintenant les techniques d'animation avancées
(cf "anim5.asm"): Première chose, au début de la procédure "UpdateFrame",
on va placer dans les variables "CurrentTickCount" et "LastTickCount"
(repectivement) le TickCount actuel et le TickCount de l'image précédente:

mov     eax, CurrentTickCount
mov     LastTickCount, eax
invoke  GetTickCount
mov     CurrentTickCount, eax

Pour animer les hélices de l'hélicoptère, on regarde tout simplement si
le temps écoulé entre l'image actuelle et l'image précédente est au moins
d'un certain nombre de millisecondes (ici 33 ms <=> 30 Hz).
Si c'est le cas, on demande d'afficher l'étape suivante de l'animation;
si ce n'est pas le cas, on affiche toujours la même:

mov     eax, CurrentTickCount
sub     eax, 33
.if eax > TickCountHelico
        add     eax, 33
        mov     TickCountHelico, eax
        inc     anim
        .if anim >= 30
                mov     anim, 0
        .endif
.endif

       Notez que cette technique, si le taux de rafraîchissment est trop, ne va pas "sauter" des étapes de l'animation.
       Par exemple si le taux est de 15 Hz, vous n'aurez pas une image sur deux. Ceci n'est pas très imporant puisque
       c'est juste une animation et qu'elle deviendrait incompréhensible si on sautait des images.
       Cependant si vous voulez le faire il suffit de faire un ".while ... .endw" en retirant 33 à TickCountHelico à chaque fois
       jusqu'à ce que la différence soit plus petite que 33.
       Ensuite pour déplacer l'hélicoptère de gauche à droite et de droite à gauche, le plus simple est d'utiliser des réels,
       ou quelque chose de semblable. Dans notre exemple on va utiliser des réels décimaux, c'est à dire que la virgule sera fixe.
       On va placer la partie décimale dans les 10 premiers bits et la partie entière dans les 22 bits restants.
       Ainsi on a une précision d'environ 3 chiffres après la virgule, ce qui est suffisant.
       L'avantage des réels décimaux est qu'il sont presqu'aussi simple et rapide à manipuler que les entiers.

       Des exemples:
        1=1024 (dec), 400 (hex) 0,5=512 (dec), 200 (hex) 1,5=1536 (dec), 600 (hex)

       Pour convertir un entier en un réel décimal, il suffit de le décaler de
       10 bits vers la droite:
       1 = 1 shl 10540 = 540 shl 10
       Pour additionner et soustraire des réels décimaux, on procède comme pour
       les entiers. Pour la multiplication et division entre 2 réels décimaux,
       il faut après chaque calcul rétablir la position de la virgule:
       2 shl 10 * 3 shl 10 = 6 shl 20 => (6 shl 20) shr 10 =>
       6 shl 106 shl 10 / 2 shl 10 = 3 => 3 shl 10

Par contre pour la multiplication et la division d'un entier avec un réel décimal, il n'est pas nécéssaire de rétablir la
position de la virgule: 2 shl 10 * 3 => 6 shl 106 shl 10 / 2 => 3 shl 10
Pour déplacer note hélicoptère, nous allons procéder comme précédemment, mais au lieu de toujours ajouter le même nombre de pixel,
nous allons ajouter le nombre exact de pixel (même s'il n'est pas entier) que doît parcourir le sprite:

mov     eax, CurrentTickCount
sub     eax, LastTickCount
imul    eax, HelicoSens
add     HelicoX, eax

HelicoSens va contenir deux informations: le sens (signe) et le nombre de
pixels par seconde à parcourir. HelicoSens est un entier
(vouloir faire moins de 1 pixel par seconde semble complètement inutile
dans notre exemple) donc la multiplication du nombre de millisecondes par
HelicoSens nous donne à peu de chose près un réel décimal.
En effet 1024 c'est presque 1000. Si vous voulez être super précis il faut
multiplier le résultat par 1024 / 1000 = 1.024, mais cela ne risque pas de
changer grand chose...Ensuite pour contrôler que l'hélicoptère ne sort pas
de l'écran, il faut juste tenir compte du fait que HelicoX est un réel
décimal:

cmp     HelicoX, 0
jge     @F
neg     HelicoSens
mov     HelicoX, 0
@@:
cmp     HelicoX, 540 shl 10
jle     @F
neg     HelicoSens
mov     HelicoX, 540 shl 10
@@:

De même pour le calcul de sa position:

mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
mov     ebx, anim
mov     eax, tblHelicoX[ebx*4]
mov     rect.left, eax
add     eax, 98
mov     rect.right, eax
mov     ebx, anim
mov     eax, tblHelicoY[ebx*4]
mov     rect.top, eax
add     eax, 58
mov     rect.bottom, eax
mov     eax, HelicoX
shr     eax, 10
mov     rectdest.left, eax
add     eax, 98
mov     rectdest.right, eax
mov     rectdest.top, 302
mov     rectdest.bottom, 302+58

invoke  Clear,addr ddbltfx,size ddbltfx
mov     ddbltfx.dwSize, size ddbltfx
cmp     HelicoSens, 0
jle     @F
mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
@@:
DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or DDBLT_KEYSRC or DDBLT_DDFX,ddbltfx

Précédent / Suivant


Le bon choix

Draw est indispensable pour faire des jeux et des démos sous windows,
mais il ne faut pas pour autant se jeter dedans.Il y a autant de très bon
que de très mauvais, le plus dur étant de savoir. Les conseils que je vais
vous donner ne seront peut-être pas les meilleurs étant donné que je n'ai
pu tester DirectDraw que sur mes deux machines (une vieille et une moins
vieille), mais j'espère qu'il vous aideront à trouver les solutions
optimales.La première chose à prendre sans réfléchir c'est le Double et le
Triple Buffering: là je suis totalement convaincu que microsoft a fait du
bon boulot, c'est simple et terriblement efficace.
Le choix entre le Double et le Triple Buffering sera uniquement fonction
de la quantité de mémoire vidéo que vous voudrez utiliser. Ensuite vient
un premier problème: le rendu de l'image. Faut-il faire systématiquement
appel aux fonctions de DirectDraw ? Il est indéniable qu'en présence d'une
bonne carte accélératrice, aussi mal optimizé que soit DirectDraw,
les performances seront de très loin supérieures à la meilleure des
algorithmes sur processeur central. Mais dans l'autre cas, j'ai eu la
chance de pourvoir tester sur une simple carte 2D:
le résultat fait tout simplement peur. Vous n'aurez même pas à chercher la
super-algo pour suplanter les performances de DirectDraw. Et je ne pense
pas qu'il faille oublier ce genre de cartes graphique, tout simplement
puisqu'on voit de plus en plus de configuration à bas prix avec des cartes
graphisques intégrés à la carte mère faisant entièrement appel au processeur
central.Donc, mon conseil: faites toujours 2 versions, une qui fait appel
aux fonctions DirectDraw et une autre qui gère tout seule (je rappelle que
je parle du rendu, il faut toujours garder le Double Buffering).
Maintenant je vais considérer que nous parlons que des décisions à prendre
lorsqu'il y a une carte accélératrice, pour l'autre cas il faut absolument
tout faire soi-même.Il y a aussi le stockage des sprites:
faut-il les stocker en mémoire vidéo ou en mémoire système ? Si vous
stockez en mémoire vidéo, le rendu via les fonctions DirectDraw sera
beaucoup plus rapide, mais si vous voulez générer des rendus vous-même
que DirectDraw ne supporte pas, il est préférable d'utiliser la mémoire
système. (Pour choisir si une surface est en mémoire physique ou vidéo,
il faut rajouter DDSCAPS_SYSTEMMEMORY ou DDSCAPS_VIDEOMEMORY dans le champs
dwCaps de la structure DDSCAPS2 que vous utilisez pour créer une surface).
Prenons un exemple: vous voulez faire un jeux de pacman, avec les briques
pour le décor, des fantômes et bien sûr un pacman...
Le tout posé simplement avec des blits simple et avec tranparence.
Mon conseil: tout en mémoire vidéo, on utilise à 100 % DirectDraw.
Un autre exemple: vous voulez faire un mode de rendu "software" d'un jeux 3D
(c'est à dire que vous n'utilisez pas de carte 3D).
Si vous faites une image avec de noubreux effets (lumière, anti-aliasing,
etc...) qui requiert énormément d'accès à la mémoire des surfaces, il faut
absolument les stocker en mémoire physique. Je vous conseillerais même de
tout faire entièrement vous-même en mémoire physique, puis de demander
à DirectDraw de copier l'image en mémoire physique dans la surface de fond !
Ceci peut paraître un peu stupide, mais avec du Triple Buffering, les tests
que j'ai fait m'ont montré qu'il était préférable de faire comme ça.
Ceci dit la différence n'était pas extraordinaire, j'avais un gain
de 5 à 10 % de vitesse.Il ne faut pas oublier aussi que la mémoire vidéo
est beaucoup moins importante que la mémoire physique ! Même sur une bonne
carte 3D la mémoire physique est souvent 4 à 8 fois plus importante !
Donc si vous avez l'intention d'utiliser 100 Mo de sprites... Il faut
choisir la mémoire physique !
J'espère à présent que vous ferez le bon choix... Précédent


Anim1.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Anim 1",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow        db "Impossible de créer la fenêtre",0
eDirectDrawCreate    db "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode      db "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface       db "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface  db "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer               db "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

tblHelicoX      dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
tblHelicoY      dd      77,77,77,77,77,77
                dd      137,137,137,137,137,137
                dd      197,197,197,197,197,197
                dd      257,257,257,257,257,257
                dd      317,317,317,317,317,317

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?
anim            dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT,ddcolorkey:DDCOLORKEY

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple avec colorkey
        mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
        mov     ddcolorkey.dwColorSpaceHighValue, 0000000h
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; animation
        inc     anim
        .if anim >= 30
                mov     anim, 0
        .endif

        mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
        mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     ebx, anim
        mov     eax, tblHelicoX[ebx*4]
        mov     rect.left, eax
        add     eax, 98
        mov     rect.right, eax
        mov     ebx, anim
        mov     eax, tblHelicoY[ebx*4]
        mov     rect.top, eax
        add     eax, 58
        mov     rect.bottom, eax
        DDS7_BltFast pDDSBack,270,302,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif

        DD7_SetDisplayMode pDD,640,480,16,0,0

        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif

        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,50,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Anim2.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Anim 2",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

tblHelicoX      dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
tblHelicoY      dd      77,77,77,77,77,77
                dd      137,137,137,137,137,137
                dd      197,197,197,197,197,197
                dd      257,257,257,257,257,257
                dd      317,317,317,317,317,317

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?
anim            dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT,rectdest:RECT,ddcolorkey:DDCOLORKEY

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple avec colorkey
        mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
        mov     ddcolorkey.dwColorSpaceHighValue, 0000000h
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; animation
        inc     anim
        .if anim >= 30
                mov     anim, 0
        .endif

        mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
        mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     ebx, anim
        mov     eax, tblHelicoX[ebx*4]
        mov     rect.left, eax
        add     eax, 98
        mov     rect.right, eax
        mov     ebx, anim
        mov     eax, tblHelicoY[ebx*4]
        mov     rect.top, eax
        add     eax, 58
        mov     rect.bottom, eax

        mov     rectdest.left, 270
        mov     rectdest.top, 302
        mov     rectdest.right, 270+98
        mov     rectdest.bottom, 302+58

        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
        DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or DDBLT_KEYSRC or DDBLT_DDFX,ddbltfx

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,50,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Anim3.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Anim 3",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

tblHelicoX      dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
tblHelicoY      dd      77,77,77,77,77,77
                dd      137,137,137,137,137,137
                dd      197,197,197,197,197,197
                dd      257,257,257,257,257,257
                dd      317,317,317,317,317,317

HelicoX         dd              0
HelicoSens      dd              5

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?
anim            dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT,rectdest:RECT,ddcolorkey:DDCOLORKEY

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple avec colorkey
        mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
        mov     ddcolorkey.dwColorSpaceHighValue, 0000000h
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; animation
        inc     anim
        .if anim >= 30
                mov     anim, 0
        .endif
        mov     eax, HelicoSens
        add     HelicoX, eax
        cmp     HelicoX, 0
        jge     @F
        neg     HelicoSens
        mov     HelicoX, 0
@@:
        cmp     HelicoX, 540
        jle     @F
        neg     HelicoSens
        mov     HelicoX, 540
@@:
        mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
        mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     ebx, anim
        mov     eax, tblHelicoX[ebx*4]
        mov     rect.left, eax
        add     eax, 98
        mov     rect.right, eax
        mov     ebx, anim
        mov     eax, tblHelicoY[ebx*4]
        mov     rect.top, eax
        add     eax, 58
        mov     rect.bottom, eax

        mov     eax, HelicoX
        mov     rectdest.left, eax
        add     eax, 98
        mov     rectdest.right, eax
        mov     rectdest.top, 302
        mov     rectdest.bottom, 302+58

        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        cmp     HelicoSens, 0
        jle     @F
        mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
@@:
        DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or DDBLT_KEYSRC or DDBLT_DDFX,ddbltfx

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,50,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Anim4.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Anim 4",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

tblHelicoX      dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
tblHelicoY      dd      77,77,77,77,77,77
                dd      137,137,137,137,137,137
                dd      197,197,197,197,197,197
                dd      257,257,257,257,257,257
                dd      317,317,317,317,317,317

HelicoX         dd              0
HelicoSens      dd              5

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?
anim            dd              ?

CurrentTickCount dd             ?
LastTickCount   dd              ?
TickCountRate   dd              ?
ips             dd              ?
lastips         dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT,rectdest:RECT,ddcolorkey:DDCOLORKEY
LOCAL   buf[100h]:BYTE

        mov     eax, CurrentTickCount
        mov     LastTickCount, eax
        invoke  GetTickCount
        mov     CurrentTickCount, eax

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple avec colorkey
        mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
        mov     ddcolorkey.dwColorSpaceHighValue, 0000000h
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; animation
        inc     anim
        .if anim >= 30
                mov     anim, 0
        .endif
        mov     eax, HelicoSens
        add     HelicoX, eax
        cmp     HelicoX, 0
        jge     @F
        neg     HelicoSens
        mov     HelicoX, 0
@@:
        cmp     HelicoX, 540
        jle     @F
        neg     HelicoSens
        mov     HelicoX, 540
@@:
        mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
        mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     ebx, anim
        mov     eax, tblHelicoX[ebx*4]
        mov     rect.left, eax
        add     eax, 98
        mov     rect.right, eax
        mov     ebx, anim
        mov     eax, tblHelicoY[ebx*4]
        mov     rect.top, eax
        add     eax, 58
        mov     rect.bottom, eax

        mov     eax, HelicoX
        mov     rectdest.left, eax
        add     eax, 98
        mov     rectdest.right, eax
        mov     rectdest.top, 302
        mov     rectdest.bottom, 302+58

        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        cmp     HelicoSens, 0
        jle     @F
        mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
@@:
        DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or DDBLT_KEYSRC or DDBLT_DDFX,ddbltfx

; calcule le nombre d'image par secondes
        inc     ips
        mov     eax, CurrentTickCount
        sub     eax, 1000
        .if eax > TickCountRate
                add     eax, 1000
                mov     TickCountRate, eax
                mov     eax, ips
                mov     lastips, eax
                mov     ips, 0
        .endif

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
; affiche le nombre d'images par seconde
                invoke  Dec2Str,lastips,addr buf
                invoke  StrLen,addr buf
                invoke  TextOut,hDC,0,0,addr buf,eax

                DDS7_ReleaseDC pDDSBack,hDC
        .endif

; flip
        .while TRUE
                DDS7_Flip pDDSPrimary,NULL,0
                .if eax == DD_OK
                        .break
                .elseif eax == DDERR_SURFACELOST
                        DDS7_Restore pDDSPrimary
                        .break .if eax != DD_OK
                .elseif eax == DDERR_WASSTILLDRAWING
                        .break
                .endif
        .endw

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; boucle des messages
        .while  TRUE
                invoke  PeekMessage,addr msg,NULL,0,0,PM_NOREMOVE
                .if eax
                        invoke  GetMessage,addr msg,NULL,0,0
                        .break .if !eax
                        invoke  TranslateMessage,addr msg
                        invoke  DispatchMessage,addr msg
                .elseif bActive
                        invoke  UpdateFrame,hWindow
                .else
                        invoke  WaitMessage
                .endif
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Anim5.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Anim 5",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

tblHelicoX      dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
                dd      1,101,201,301,401,501
tblHelicoY      dd      77,77,77,77,77,77
                dd      137,137,137,137,137,137
                dd      197,197,197,197,197,197
                dd      257,257,257,257,257,257
                dd      317,317,317,317,317,317

HelicoX         dd              0
HelicoSens      dd              200

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?
anim            dd              ?

TickCountHelico dd              ?
CurrentTickCount dd             ?
LastTickCount   dd              ?
TickCountRate   dd              ?
ips             dd              ?
lastips         dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT,rectdest:RECT,ddcolorkey:DDCOLORKEY
LOCAL   buf[100h]:BYTE

        mov     eax, CurrentTickCount
        mov     LastTickCount, eax
        invoke  GetTickCount
        mov     CurrentTickCount, eax

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple avec colorkey
        mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
        mov     ddcolorkey.dwColorSpaceHighValue, 0000000h
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; animation
        mov     eax, CurrentTickCount
        sub     eax, 33
        .if eax > TickCountHelico
                add     eax, 33
                mov     TickCountHelico, eax
                inc     anim
                .if anim >= 30
                        mov     anim, 0
                .endif
        .endif
        mov     eax, CurrentTickCount
        sub     eax, LastTickCount
        imul    eax, HelicoSens
        add     HelicoX, eax
        cmp     HelicoX, 0
        jge     @F
        neg     HelicoSens
        mov     HelicoX, 0
@@:
        cmp     HelicoX, 540 shl 10
        jle     @F
        neg     HelicoSens
        mov     HelicoX, 540 shl 10
@@:
        mov     ddcolorkey.dwColorSpaceLowValue, 000001fh
        mov     ddcolorkey.dwColorSpaceHighValue, 000001fh
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     ebx, anim
        mov     eax, tblHelicoX[ebx*4]
        mov     rect.left, eax
        add     eax, 98
        mov     rect.right, eax
        mov     ebx, anim
        mov     eax, tblHelicoY[ebx*4]
        mov     rect.top, eax
        add     eax, 58
        mov     rect.bottom, eax

        mov     eax, HelicoX
        shr     eax, 10
        mov     rectdest.left, eax
        add     eax, 98
        mov     rectdest.right, eax
        mov     rectdest.top, 302
        mov     rectdest.bottom, 302+58

        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        cmp     HelicoSens, 0
        jle     @F
        mov     ddbltfx.dwDDFX, DDBLTFX_MIRRORLEFTRIGHT
@@:
        DDS7_Blt pDDSBack,rectdest,pDDS2,rect,DDBLT_WAIT or DDBLT_KEYSRC or DDBLT_DDFX,ddbltfx

; calcule le nombre d'image par secondes
        inc     ips
        mov     eax, CurrentTickCount
        sub     eax, 1000
        .if eax > TickCountRate
                add     eax, 1000
                mov     TickCountRate, eax
                mov     eax, ips
                mov     lastips, eax
                mov     ips, 0
        .endif

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
; affiche le nombre d'images par seconde
                invoke  Dec2Str,lastips,addr buf
                invoke  StrLen,addr buf
                invoke  TextOut,hDC,0,0,addr buf,eax

                DDS7_ReleaseDC pDDSBack,hDC
        .endif

; flip
        .while TRUE
                DDS7_Flip pDDSPrimary,NULL,0
                .if eax == DD_OK
                        .break
                .elseif eax == DDERR_SURFACELOST
                        DDS7_Restore pDDSPrimary
                        .break .if eax != DD_OK
                .elseif eax == DDERR_WASSTILLDRAWING
                        .break
                .endif
        .endw

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; boucle des messages
        .while  TRUE
                invoke  PeekMessage,addr msg,NULL,0,0,PM_NOREMOVE
                .if eax
                        invoke  GetMessage,addr msg,NULL,0,0
                        .break .if !eax
                        invoke  TranslateMessage,addr msg
                        invoke  DispatchMessage,addr msg
                .elseif bActive
                        invoke  UpdateFrame,hWindow
                .else
                        invoke  WaitMessage
                .endif
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Blit1.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Blit 1",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_NOCOLORKEY

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL

        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif

        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,500,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Blit2.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Blit 2",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF1            db      "ddraw1.gif",0
sFichierGIF2            db      "ddraw2.gif",0
Message                 db      "Pressez ESC pour quitter",0

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
pDDS2           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                .if pDDS2 != NULL
                        Release pDDS2
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT,ddcolorkey:DDCOLORKEY

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

; blit simple avec colorkey
        mov     ddcolorkey.dwColorSpaceLowValue, 0000000h
        mov     ddcolorkey.dwColorSpaceHighValue, 0000000h
        DDS7_SetColorKey pDDS2,DDCKEY_SRCBLT,ddcolorkey
        mov     rect.left, 0
        mov     rect.top, 0
        mov     rect.right, 510
        mov     rect.bottom, 75
        DDS7_BltFast pDDSBack,65,202,pDDS2,rect,DDBLTFAST_SRCCOLORKEY

; ***

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF1,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS2,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF2,640,480,pDDS2,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,500,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Flip.asm

include winapi.inc
include ddraw.inc

.data
AppName         db      "Page Flipping",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

Front           db      "Première Surface",0
Back            db      "Deuxième Surface",0
Essai           db      "Essai. Pressez ESC pour quitter",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?

.const

.code

; ---------------------------------------------------------------------------------
;    Procédures utiles
Clear   proc    buf1:LPVOID,len1:DWORD
        pushad
        mov     edi, buf1
        mov     ecx, len1
        xor     eax, eax
        rep     stosb
        popad
        ret
Clear   endp

StrLen  proc    str1:LPSTR
        push    esi
        push    ecx
        mov     esi, str1
        xor     ecx, ecx
@@:
        lodsb
        inc     ecx
        or      al, al
        jnz     @B
        dec     ecx
        mov     eax, ecx
        pop     ecx
        pop     esi
        ret
StrLen  endp

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT
        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        mov     ddbltfx.dwFillColor, 0
        DDS7_Blt pDDSBack,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,ddbltfx

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,00000FFh
                invoke  SetTextColor,hDC,0FFFF00h
                .if phase
                        mov     phase, 0
                        invoke  StrLen,addr Front
                        invoke  TextOut,hDC,0,0,addr Front,eax

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Essai,-1,addr rect,\
                                 DT_SINGLELINE or DT_CENTER or DT_VCENTER
                .else
                        mov     phase, 1
                        invoke  StrLen,addr Back
                        invoke  TextOut,hDC,0,0,addr Back,eax
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,2000,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Gif.asm

include winapi.inc
include ddraw.inc
include units.inc

.data
AppName         db      "Charger un Fichier GIF",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

sFichierGIF             db      "DDRAW1.gif",0 ;;      "ddraw1.gif",0
Message                 db      "Pressez ESC pour quitter",0

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>
eErreur         db              200h dup (?)

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
pDDS1           LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?
phase           dd              ?

.const

.code

include string.inc
include graph.inc

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                .if pDDS1 != NULL
                        Release pDDS1
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

UpdateFrame     proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT

        invoke  GetClientRect,hWnd,addr rect
        DDS7_BltFast pDDSBack,0,0,pDDS1,rect,DDBLTFAST_NOCOLORKEY

        DDS7_GetDC pDDSBack,hDC
        .if eax == DD_OK
                invoke  SetBkColor,hDC,0F0F0F0h
                invoke  SetTextColor,hDC,0000000h
                .if phase
                        mov     phase, 0

                        invoke  GetClientRect,hWnd,addr rect
                        invoke  DrawText,hDC,addr Message,-1,addr rect,\
                                 DT_SINGLELINE or DT_RIGHT or DT_BOTTOM
                .else
                        mov     phase, 1
                .endif
                DDS7_ReleaseDC pDDSBack,hDC
        .endif

        ret
UpdateFrame     endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        invoke  UpdateFrame,hWnd
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 1
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif
; créé une surface
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_HEIGHT or DDSD_WIDTH
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_OFFSCREENPLAIN
        mov     ddsd.dwWidth, 640
        mov     ddsd.dwHeight, 480
        DD7_CreateSurface pDD,ddsd,pDDS1,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
; charge un fichier GIF dans la suface
        invoke  ChargerGIF,addr sFichierGIF,640,480,pDDS1,addr eErreur
        .if !eax
                invoke  Erreur,hWindow,addr eErreur
        .endif

; création d'un timer
        invoke  SetTimer,hWindow,1,500,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Simple.asm

include winapi.inc
include ddraw.inc

.data
AppName         db      "Direct Draw Simple",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow        db "Impossible de créer la fenêtre",0
eDirectDrawCreate    db "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode      db "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface       db "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface  db "Erreur DirectDraw: GetAttachedSurface echoué",0

Essai                db "Essai. Pressez ESC pour quitter",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>

.const

.code

; ---------------------------------------------------------------------------------
;    Procédures utiles
Clear   proc    buf1:LPVOID,len1:DWORD
        pushad
        mov     edi, buf1
        mov     ecx, len1
        xor     eax, eax
        rep     stosb
        popad
        ret
Clear   endp

StrLen  proc    str1:LPSTR
        push    esi
        push    ecx
        mov     esi, str1
        xor     ecx, ecx
@@:
        lodsb
        inc     ecx
        or      al, al
        jnz     @B
        dec     ecx
        mov     eax, ecx
        pop     ecx
        pop     esi
        ret
StrLen  endp

; ---------------------------------------------------------------------------------
;    Dessine l'image

DrawFrame       proc    hWnd:HWND
LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT

        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        mov     ddbltfx.dwFillColor, 0
        DDS7_Blt pDDSPrimary,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,ddbltfx

        DDS7_GetDC pDDSPrimary,hDC
       .if eax == DD_OK
        invoke  SetBkColor,hDC,00000FFh
        invoke  SetTextColor,hDC,0FFFF00h

        invoke  GetClientRect,hWnd,addr rect
        invoke  DrawText,hDC,addr Essai,-1,addr rect,DT_SINGLELINE or DT_CENTER or DT_VCENTER

        DDS7_ReleaseDC pDDSPrimary,hDC
       .endif

        ret
DrawFrame       endp

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif

        invoke  DrawFrame,hWindow

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


Triple.asm

include winapi.inc
include ddraw.inc

.data
AppName         db      "Triple Buffering",0
ClassName       db      "DirectDrawExemple",0

eCreateWindow   db      "Impossible de créer la fenêtre",0
eDirectDrawCreate db    "Impossible d'initialiser DirectDraw",0
eSetCooperativeLevel db "Erreur DirectDraw: SetCooperativeLevel echoué",0
eSetDisplayMode db      "Erreur DirectDraw: SetDisplayMode echoué",0
eCreateSurface  db      "Erreur DirectDraw: CreateSurface echoué",0
eGetAttachedSurface db  "Erreur DirectDraw: GetAttachedSurface echoué",0
eTimer          db      "Impossible d'initialiser le timer",0

Surface1        db      "Première Surface",0
Surface2        db      "Deuxième Surface",0
Surface3        db      "Troisième Surface",0
Essai           db      "Essai. Pressez ESC pour quitter",0

IID_IDIRECTDRAW7        dd      015e65ec0h
                        dw      03b9ch,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh
IID_IDIRECTDRAWSURFACE7 dd      006675a80h
                        dw      03b9bh,011d2h
                        db      0b9h,02fh,000h,060h,097h,097h,0eah,05bh

.data?
hInstance       HINSTANCE       ?
wc              WNDCLASSEX      <>
hWindow         HWND            ?
msg             MSG             <>

ScreenX         dd              ?
ScreenY         dd              ?

pDD             LPDIRECTDRAW7           ?
pDDSPrimary     LPDIRECTDRAWSURFACE7    ?
pDDSBack        LPDIRECTDRAWSURFACE7    ?
ddsd            DDSURFACEDESC2          <>
ddscaps         DDSCAPS2                <>

bActive         dd              ?

.const

.code

; ---------------------------------------------------------------------------------
;    Procédures utiles
Clear   proc    buf1:LPVOID,len1:DWORD
        pushad
        mov     edi, buf1
        mov     ecx, len1
        xor     eax, eax
        rep     stosb
        popad
        ret
Clear   endp

StrLen  proc    str1:LPSTR
        push    esi
        push    ecx
        mov     esi, str1
        xor     ecx, ecx
@@:
        lodsb
        inc     ecx
        or      al, al
        jnz     @B
        dec     ecx
        mov     eax, ecx
        pop     ecx
        pop     esi
        ret
StrLen  endp

; ---------------------------------------------------------------------------------
;    libère tous les objets

LibereObjets    proc
        .if pDD != NULL
                .if pDDSPrimary != NULL
                        Release pDDSPrimary
                .endif
                Release pDD
        .endif
        ret
LibereObjets    endp

; ---------------------------------------------------------------------------------
;    gestion des erreurs

Erreur  proc    hWnd:HWND,message:LPSTR

        invoke  LibereObjets
        invoke  MessageBox,hWnd,message,addr AppName,MB_OK
        invoke  DestroyWindow,hWnd

        invoke  ExitProcess,-1
Erreur  endp

; ---------------------------------------------------------------------------------
;    Création de l'image

DrawFrames     proc    hWnd:HWND

LOCAL   hDC:HDC,ddbltfx:DDBLTFX,rect:RECT

; on rempli le premier buffer
        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        mov     ddbltfx.dwFillColor, 0
        DDS7_Blt pDDSBack,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,ddbltfx
        DDS7_GetDC pDDSBack,hDC

        invoke  SetBkColor,hDC,00000FFh
        invoke  SetTextColor,hDC,0FFFF00h

        invoke  StrLen,addr Surface1
        invoke  TextOut,hDC,0,0,addr Surface1,eax

        invoke  GetClientRect,hWnd,addr rect
        invoke  DrawText,hDC,addr Essai,-1,addr rect,\
                 DT_SINGLELINE or DT_CENTER or DT_VCENTER
        DDS7_ReleaseDC pDDSBack,hDC

; on passe de l'un à l'autre

        DDS7_Flip pDDSPrimary,NULL,0

; puis on rempli le second
        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        mov     ddbltfx.dwFillColor, 0
        DDS7_Blt pDDSBack,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,ddbltfx

        DDS7_GetDC pDDSBack,hDC
        invoke  SetBkColor,hDC,00000FFh
        invoke  SetTextColor,hDC,0FFFF00h

        invoke  StrLen,addr Surface2
        invoke  TextOut,hDC,0,0,addr Surface2,eax

        DDS7_ReleaseDC pDDSBack,hDC

; on passe de l'un à l'autre

        DDS7_Flip pDDSPrimary,NULL,0

; puis on rempli le troisième
        invoke  Clear,addr ddbltfx,size ddbltfx
        mov     ddbltfx.dwSize, size ddbltfx
        mov     ddbltfx.dwFillColor, 0
        DDS7_Blt pDDSBack,NULL,NULL,NULL,DDBLT_COLORFILL or DDBLT_WAIT,ddbltfx

        DDS7_GetDC pDDSBack,hDC
        invoke  SetBkColor,hDC,00000FFh
        invoke  SetTextColor,hDC,0FFFF00h

        invoke  StrLen,addr Surface3
        invoke  TextOut,hDC,0,0,addr Surface3,eax

        DDS7_ReleaseDC pDDSBack,hDC

        ret
DrawFrames    endp

; ---------------------------------------------------------------------------------
;    gestion de la fenêtre

WindowProc      proc    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
LOCAL   hDC:HDC
        mov     eax, uMsg
        .if eax == WM_DESTROY
                invoke  LibereObjets
                invoke  PostQuitMessage,NULL
                xor     eax, eax
        .elseif eax == WM_ACTIVATE
                mov     eax, wParam
                shr     eax, 16
                not     eax
                mov     bActive, eax
        .elseif eax == WM_KEYDOWN
                .if wParam == VK_ESCAPE
                        invoke  PostMessage,hWnd,WM_CLOSE,0,0
                .endif
        .elseif eax == WM_SETCURSOR
                invoke  SetCursor,NULL
        .elseif eax == WM_TIMER
                .if wParam == 1 && bActive
                        .while TRUE
                                DDS7_Flip pDDSPrimary,NULL,0
                                .if eax == DD_OK
                                        .break
                                .elseif eax == DDERR_SURFACELOST
                                        DDS7_Restore pDDSPrimary
                                        .break .if eax != DD_OK
                                .elseif eax == DDERR_WASSTILLDRAWING
                                        .break
                                .endif
                        .endw
                .endif
        .else
                invoke  DefWindowProc,hWnd,uMsg,wParam,lParam
        .endif
        ret
WindowProc      endp

; ---------------------------------------------------------------------------------
;   corps du programme

start:
        invoke  GetModuleHandle,NULL
        mov     hInstance, eax

        invoke  GetSystemMetrics,SM_CXSCREEN
        mov     ScreenX, eax
        invoke  GetSystemMetrics,SM_CYSCREEN
        mov     ScreenY, eax

; Création de la fenêtre
        mov     wc.cbSize, sizeof WNDCLASSEX
        mov     wc.style, CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc, offset WindowProc
        mov     wc.cbClsExtra, NULL
        mov     wc.cbWndExtra, NULL
        push    hInstance
        pop     wc.hInstance
        mov     wc.hbrBackground, COLOR_WINDOW + 1
        mov     wc.lpszMenuName, NULL
        mov     wc.lpszClassName, offset ClassName
        invoke  LoadIcon,NULL,IDI_APPLICATION
        mov     wc.hIcon, eax
        mov     wc.hIconSm, NULL
        invoke  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor, eax
        invoke  RegisterClassEx,addr wc
        invoke  CreateWindowEx,WS_EX_TOPMOST,addr ClassName,addr AppName,\
                 WS_POPUP,\
                 0,0,ScreenX,ScreenY,\
                 NULL,NULL,hInstance,NULL
        .if !eax
                invoke  MessageBox,NULL,addr eCreateWindow,addr AppName,MB_OK
                invoke  ExitProcess,-1
        .endif
        mov     hWindow, eax
        invoke  ShowWindow,hWindow,SW_SHOWDEFAULT
        invoke  UpdateWindow,hWindow
        invoke  SetFocus,hWindow

; initialisation de DirectDraw

        invoke  DirectDrawCreateEx,NULL,addr pDD,addr IID_IDIRECTDRAW7,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eDirectDrawCreate
        .endif

        DD7_SetCooperativeLevel pDD,hWindow,DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetCooperativeLevel
        .endif
        DD7_SetDisplayMode pDD,640,480,16,0,0
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eSetDisplayMode
        .endif
        invoke  Clear,addr ddsd,size ddsd
        mov     ddsd.dwSize, size ddsd
        mov     ddsd.dwFlags, DDSD_CAPS or DDSD_BACKBUFFERCOUNT
        mov     ddsd.ddsCaps.dwCaps, DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX
        mov     ddsd.dwBackBufferCount, 2
        DD7_CreateSurface pDD,ddsd,pDDSPrimary,NULL
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eCreateSurface
        .endif
        invoke  Clear,addr ddscaps,size ddscaps
        mov     ddscaps.dwCaps, DDSCAPS_BACKBUFFER
        DDS7_GetAttachedSurface pDDSPrimary,ddscaps,pDDSBack
        .if eax != DD_OK
                invoke  Erreur,hWindow,addr eGetAttachedSurface
        .endif

; on remplie les 3 buffers

        invoke  DrawFrames,hWindow

; création d'un timer
        invoke  SetTimer,hWindow,1,2  500,NULL
        .if eax != 1
                invoke  Erreur,hWindow,addr eTimer
        .endif

; boucle des messages
        .while  TRUE
                invoke  GetMessage,addr msg,NULL,0,0
                .break .if !eax
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
        .endw
        invoke  ExitProcess,msg.wParam

        end     start


ddraw.INC

includelib      ddraw.lib

LPDIRECTDRAWSURFACE     typedef PTR DWORD

DDCOLORKEY      struct
dwColorSpaceLowValue    DWORD           ?
dwColorSpaceHighValue   DWORD           ?
DDCOLORKEY      ends

DDSCAPS2        struct
dwCaps          DWORD   ?
dwCaps2         DWORD   ?
dwCaps3         DWORD   ?
dwCaps4         DWORD   ?
DDSCAPS2        ends

DDPIXELFORMAT   struct
        dwSize                  DWORD           ?
        dwFlags                 DWORD           ?
        dwFourCC                DWORD           ?
union
        dwRGBBitCount           DWORD           ?
        dwYUVBitCount           DWORD           ?
        dwZBufferBitDepth       DWORD           ?
        dwAlphaBitDepth         DWORD           ?
        dwLuminanceBitCount     DWORD           ?
        dwBumpBitCound          DWORD           ?
ends
union
        dwRBitMask              DWORD           ?
        dwYBitMask              DWORD           ?
        dwStencilBitDepth       DWORD           ?
        dwLuminanceBitMask      DWORD           ?
        dwBumpDuBitMask         DWORD           ?
ends
union
        dwGBitMask              DWORD           ?
        dwUBitMask              DWORD           ?
        dwZBitMask              DWORD           ?
        dwBumpDvBitMask         DWORD           ?
ends
union
        dwBBitMask              DWORD           ?
        dwVBitMask              DWORD           ?
        dwStencilBitMask        DWORD           ?
        dwBumpLuminanceBitMask  DWORD           ?
ends
union
        dwRGBAlphaBitMask       DWORD           ?
        dwYUVAlphaBitMask       DWORD           ?
        dwLuminanceAlphaBitMask DWORD           ?
        dwRGBZBitMask           DWORD           ?
        dwYUVZBitMask           DWORD           ?
ends
DDPIXELFORMAT   ends

DDSURFACEDESC2  struct
        dwSize                  DWORD           ?
        dwFlags                 DWORD           ?
        dwHeight                DWORD           ?
        dwWidth                 DWORD           ?
union
        lPitch                  LONG            ?
        dwLinearSize            DWORD           ?
ends
        dwBackBufferCount       DWORD           ?
union
        dwMipMapCount           DWORD           ?
        dwRefreshRate           DWORD           ?
        dwSrcVBHandle           DWORD           ?
ends
        dwAlphaBitDepth         DWORD           ?
        dwReserved              DWORD           ?
        lpSurface               LPVOID          ?
union
        ddckCKDestOverlay       DDCOLORKEY      <>
        dwEmptyFaceColor        DWORD           ?
ends
        ddckCKDestBlt           DDCOLORKEY      <>
        ddckCKSrcOverlay        DDCOLORKEY      <>
        ddckSrcBlt              DDCOLORKEY      <>
union
        ddpfPixelFormat         DDPIXELFORMAT   <>
        dwFVF                   DWORD           ?
ends
        ddsCaps                 DDSCAPS2        <>
        dwTextureStage          DWORD           ?
DDSURFACEDESC2  ends

DDBLTFX         struct
        dwSize                  DWORD           ?
        dwDDFX                  DWORD           ?
        dwROP                   DWORD           ?
        dwDDROP                 DWORD           ?
        dwRotationAngle         DWORD           ?
        dwZBufferOpCode         DWORD           ?
        dwZBufferLow            DWORD           ?
        dwZBufferHigh           DWORD           ?
        dwZBufferBaseDest       DWORD           ?
        dwZDestConstBitDepth    DWORD           ?
union
        dwZDestConst            DWORD           ?
        lpDDSZBufferDest        LPDIRECTDRAWSURFACE ?
ends
        dwZSrcConstBitDepth     DWORD           ?
union
        dwZSrcConst             DWORD           ?
        lpDDSZBufferSrc         LPDIRECTDRAWSURFACE ?
ends
        dwAlphaEdgeBlendBitDepth DWORD          ?
        dwAlphaEdgeBlend        DWORD           ?
        dwReserved              DWORD           ?
        dwAlphaDestConstBitDepth DWORD          ?
union
        dwAlphaDestConst        DWORD           ?
        lpDDSAlphaDest          LPDIRECTDRAWSURFACE ?
ends
        dwAlphaSrcConstBitDepth DWORD           ?
union
        dwAlphaSrcCont          DWORD           ?
        lpDDSAlphaSrc           LPDIRECTDRAWSURFACE ?
ends
union
        dwFillColor             DWORD           ?
        dwFillDepth             DWORD           ?
        dwFillPixel             DWORD           ?
        lpDDSPattern            DWORD           ?
ends
        ddckDestColorKey        DDCOLORKEY      <>
        ddckSrcColorKey         DDCOLORKEY      <>
DDBLTFX         ends

DD_OK           equ     0

DDBLT_ALPHADEST                         equ     000000001h
DDBLT_ALPHADESTCONSTOVERRIDE            equ     000000002h
DDBLT_ALPHADESTNEG                      equ     000000004h
DDBLT_ALPHADESTSURFACEOVERRIDE          equ     000000008h
DDBLT_ALPHAEDGEBLEND                    equ     000000010h
DDBLT_ALPHASRC                          equ     000000020h
DDBLT_ALPHASRCCONSTOVERRIDE             equ     000000040h
DDBLT_ALPHASRCNEG                       equ     000000080h
DDBLT_ALPHASRCSURFACEOVERRIDE           equ     000000100h
DDBLT_ASYNC                             equ     000000200h
DDBLT_COLORFILL                         equ     000000400h
DDBLT_DDFX                              equ     000000800h
DDBLT_DDROPS                            equ     000001000h
DDBLT_KEYDEST                           equ     000002000h
DDBLT_KEYDESTOVERRIDE                   equ     000004000h
DDBLT_KEYSRC                            equ     000008000h
DDBLT_KEYSRCOVERRIDE                    equ     000010000h
DDBLT_ROP                               equ     000020000h
DDBLT_ROTATIONANGLE                     equ     000040000h
DDBLT_ZBUFFER                           equ     000080000h
DDBLT_ZBUFFERDESTCONSTOVERRIDE          equ     000100000h
DDBLT_ZBUFFERDESTOVERRIDE               equ     000200000h
DDBLT_ZBUFFERSRCCONSTOVERRIDE           equ     000400000h
DDBLT_ZBUFFERSRCOVERRIDE                equ     000800000h
DDBLT_WAIT                              equ     001000000h
DDBLT_DEPTHFILL                         equ     002000000h
DDBLT_DONOTWAIT                         equ     008000000h

DDBLTFAST_NOCOLORKEY                    equ     000000000h
DDBLTFAST_SRCCOLORKEY                   equ     000000001h
DDBLTFAST_DESTCOLORKEY                  equ     000000002h
DDBLTFAST_WAIT                          equ     000000010h
DDBLTFAST_DONOTWAIT                     equ     000000020h

DDBLTFX_ARITHSTRETCHY                   equ     000000001h
DDBLTFX_MIRRORLEFTRIGHT                 equ     000000002h
DDBLTFX_MIRRORUPDOWN                    equ     000000004h
DDBLTFX_NOTEARING                       equ     000000008h
DDBLTFX_ROTATE180                       equ     000000010h
DDBLTFX_ROTATE270                       equ     000000020h
DDBLTFX_ROTATE90                        equ     000000040h
DDBLTFX_ZBUFFERRANGE                    equ     000000080h
DDBLTFX_ZBUFFERBASEDEST                 equ     000000100h

DDCKEY_COLORSPACE                       equ     000000001h
DDCKEY_DESTBLT                          equ     000000002h
DDCKEY_DESTOVERLAY                      equ     000000004h
DDCKEY_SRCBLT                           equ     000000008h
DDCKEY_SRCOVERLAY                       equ     000000010h

DDLOCK_SURFACEMEMORYPTR                 equ     000000000h
DDLOCK_WAIT                             equ     000000001h
DDLOCK_EVENT                            equ     000000002h
DDLOCK_READONLY                         equ     000000010h
DDLOCK_WRITEONLY                        equ     000000020h
DDLOCK_NOSYSLOCK                        equ     000000800h
DDLOCK_NOOVERWRITE                      equ     000001000h
DDLOCK_DISCARDCONTENTS                  equ     000002000h
DDLOCK_OKTOSWAP                         equ     000002000h
DDLOCK_DONOTWAIT                        equ     000004000h

DDPCAPS_4BIT                            equ     000000001h
DDPCAPS_8BITENTRIES                     equ     000000002h
DDPCAPS_8BIT                            equ     000000004h
DDPCAPS_INITIALIZE                      equ     000000000h
DDPCAPS_PRIMARYSURFACE                  equ     000000010h
DDPCAPS_PRIMARYSURFACELEFT              equ     000000020h
DDPCAPS_ALLOW256                        equ     000000040h
DDPCAPS_VSYNC                           equ     000000080h
DDPCAPS_1BIT                            equ     000000100h
DDPCAPS_2BIT                            equ     000000200h
DDPCAPS_ALPHA                           equ     000000400h

DDPF_ALPHAPIXELS                        equ     000000001h
DDPF_ALPHA                              equ     000000002h
DDPF_FOURCC                             equ     000000004h
DDPF_PALETTEINDEXED4                    equ     000000008h
DDPF_PALETTEINDEXEDTO8                  equ     000000010h
DDPF_PALETTEINDEXED8                    equ     000000020h
DDPF_RGB                                equ     000000040h
DDPF_COMPRESSED                         equ     000000080h
DDPF_RGBTOYUV                           equ     000000100h
DDPF_YUV                                equ     000000200h
DDPF_ZBUFFER                            equ     000000400h
DDPF_PALETTEINDEXED1                    equ     000000800h
DDPF_PALETTEINDEXED2                    equ     000001000h
DDPF_ZPIXELS                            equ     000002000h
DDPF_STENCILBUFFER                      equ     000004000h
DDPF_ALPHAPREMULT                       equ     000008000h
DDPF_LUMINANCE                          equ     000020000h
DDPF_BUMPLUMINANCE                      equ     000040000h
DDPF_BUMPDUDV                           equ     000080000h

DDSCL_FULLSCREEN                equ     000000001h
DDSCL_ALLOWREBOOT               equ     000000002h
DDSCL_NOWINDOWCHANGES           equ     000000004h
DDSCL_NORMAL                    equ     000000008h
DDSCL_EXCLUSIVE                 equ     000000010h
DDSCL_ALLOWMODEX                equ     000000040h
DDSCL_SETFOCUSWINDOW            equ     000000080h
DDSCL_SETDEVICEWINDOW           equ     000000100h
DDSCL_CREATEDEVICEWINDOW        equ     000000200h
DDSCL_MULTITHREADED             equ     000000400h
DDSCL_FPUSETUP                  equ     000000800h
DDSCL_FPUPRESERVE               equ     000001000h

DDSD_CAPS               equ     000000001h
DDSD_HEIGHT             equ     000000002h
DDSD_WIDTH              equ     000000004h
DDSD_PITCH              equ     000000008h
DDSD_BACKBUFFERCOUNT    equ     000000020h
DDSD_ZBUFFERBITDEPTH    equ     000000040h
DDSD_ALPHABITDEPTH      equ     000000080h
DDSD_LPSURFACE          equ     000000800h
DDSD_PIXELFORMAT        equ     000001000h
DDSD_CKDESTOVERLAY      equ     000002000h
DDSD_CKDESTBLT          equ     000004000h
DDSD_CKSRCOVERLAY       equ     000008000h
DDSD_CKSRCBLT           equ     000010000h
DDSD_MIPMAPCOUNT        equ     000020000h
DDSD_REFRESHRATE        equ     000040000h
DDSD_LINEARSIZE         equ     000080000h
DDSD_TEXTURESTAGE       equ     000100000h
DDSD_FVF                equ     000200000h
DDSD_SRCVBHANDLE        equ     000400000h
DDSD_ALL                equ     0007ff9eeh

DDSCAPS_RESERVED1               equ     000000001h
DDSCAPS_ALPHA                   equ     000000002h
DDSCAPS_BACKBUFFER              equ     000000004h
DDSCAPS_COMPLEX                 equ     000000008h
DDSCAPS_FLIP                    equ     000000010h
DDSCAPS_FRONTBUFFER             equ     000000020h
DDSCAPS_OFFSCREENPLAIN          equ     000000040h
DDSCAPS_OVERLAY                 equ     000000080h
DDSCAPS_PALETTE                 equ     000000100h
DDSCAPS_PRIMARYSURFACE          equ     000000200h
DDSCAPS_RESERVED3               equ     000000400h
DDSCAPS_SYSTEMMEMORY            equ     000000800h
DDSCAPS_TEXTURE                 equ     000001000h
DDSCAPS_3DDEVICE                equ     000002000h
DDSCAPS_VIDEOMEMORY             equ     000004000h
DDSCAPS_VISIBLE                 equ     000008000h
DDSCAPS_WRITEONLY               equ     000010000h
DDSCAPS_ZBUFFER                 equ     000020000h
DDSCAPS_OWNDC                   equ     000040000h
DDSCAPS_LIVEVIDEO               equ     000080000h
DDSCAPS_HWCODEC                 equ     000100000h
DDSCAPS_MODEX                   equ     000200000h
DDSCAPS_MIPMAP                  equ     000400000h
DDSCAPS_RESERVED2               equ     000800000h
DDSCAPS_ALLOCONLOAD             equ     004000000h
DDSCAPS_VIDEOPORT               equ     008000000h
DDSCAPS_LOCALVIDMEM             equ     010000000h
DDSCAPS_NONLOCALVIDMEM          equ     020000000h
DDSCAPS_STANDARDVGAMODE         equ     040000000h
DDSCAPS_OPTIMIZED               equ     080000000h
DDSCAPS2_HARDWAREDEINTERLACE    equ     000000002h
DDSCAPS2_HINTDYNAMIC            equ     000000004h
DDSCAPS2_HINTSTATIC             equ     000000008h
DDSCAPS2_TEXTUREMANAGE          equ     000000010h
DDSCAPS2_RESERVED1              equ     000000020h
DDSCAPS2_RESERVED2              equ     000000040h
DDSCAPS2_OPAQUE                 equ     000000080h
DDSCAPS2_HINTANTIALIASING       equ     000000100h
DDSCAPS2_CUBEMAP                equ     000000200h
DDSCAPS2_CUBEMAP_POSITIVEX      equ     000000400h
DDSCAPS2_CUBEMAP_NEGATIVEX      equ     000000800h
DDSCAPS2_CUBEMAP_POSITIVEY      equ     000001000h
DDSCAPS2_CUBEMAP_NEGATIVEY      equ     000002000h
DDSCAPS2_CUBEMAP_POSITIVEZ      equ     000004000h
DDSCAPS2_CUBEMAP_NEGATIVEZ      equ     000008000h
DDSCAPS2_CUBEMAP_ALLFACES       equ     DDSCAPS2_CUBEMAP_POSITIVEX \
                                     or DDSCAPS2_CUBEMAP_NEGATIVEX \
                                     or DDSCAPS2_CUBEMAP_POSITIVEY \
                                     or DDSCAPS2_CUBEMAP_NEGATIVEY \
                                     or DDSCAPS2_CUBEMAP_POSITIVEZ \
                                     or DDSCAPS2_CUBEMAP_NEGATIVEZ
DDSCAPS2_MIPMAPSUBLEVEL         equ     000010000h
DDSCAPS2_D3DTEXTUREMANAGE       equ     000020000h
DDSCAPS2_DONOTPERSIST           equ     000040000h
DDSCAPS2_STEREOSURFACELEFT      equ     000080000h

DD7_QUERYINTERFACE              equ     000h
DD7_ADDREF                      equ     004h
DD7_RELEASE                     equ     008h
DD7_COMPACT                     equ     00Dh
DD7_CREATECLIPPER               equ     010h
DD7_CREATEPALETTE               equ     014h
DD7_CREATESURFACE               equ     018h
DD7_DUPLICATESURFACE            equ     01Dh
DD7_ENUMDISPLAYMODES            equ     020h
DD7_ENUMSURFACES                equ     024h
DD7_FLIPTOGDISURFACE            equ     028h
DD7_GETCAPS                     equ     02Dh
DD7_GETDISPLAYMODE              equ     030h
DD7_GETFOURCCCODES              equ     034h
DD7_GETGDISURFACE               equ     038h
DD7_GETMONITORFREQUENCY         equ     03Dh
DD7_GETSCANLINE                 equ     040h
DD7_GETVERTICALBLANKSTATUS      equ     044h
DD7_INITIALIZE                  equ     048h
DD7_RESTOREDISPLAYMODE          equ     04Dh
DD7_SETCOOPERATIVELEVEL         equ     050h
DD7_SETDISPLAYMODE              equ     054h
DD7_WAITFORVERTICALBLANK        equ     058h
DD7_GETAVAILABLEVIDMEM          equ     05Dh
DD7_GETSURFACEFROMDC            equ     060h
DD7_RESTOREALLSURFACES          equ     064h
DD7_TESTCOOPERATIVELEVEL        equ     068h
DD7_GETDEVICEIDENTIFIER         equ     06Dh
DD7_STARTMODETEST               equ     070h
DD7_EVALUATEMODE                equ     074h

DD7_SetCooperativeLevel macro   pt, arg1, arg2
        mov     eax, arg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_SETCOOPERATIVELEVEL]
                endm

DD7_SetDisplayMode macro   pt, arg1, arg2, arg3, arg4, arg5
        mov     eax, arg5
        push    eax
        mov     eax, arg4
        push    eax
        mov     eax, arg3
        push    eax
        mov     eax, arg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_SETDISPLAYMODE]
                endm

DD7_CreateSurface       macro   pt, lparg1, lparg2, arg3
        mov     eax, arg3
        push    eax
        lea     eax, lparg2
        push    eax
        lea     eax, lparg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_CREATESURFACE]
                endm

DD7_GetDisplayMode  macro   pt, lparg1
        lea     eax, lparg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_GETDISPLAYMODE]
                endm

DD7_CreatePalette       macro   pt, arg1, lparg2, lparg3
        push    NULL
        lea     eax, lparg3
        push    eax
        lea     eax, lparg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DD7_CREATEPALETTE]
                endm

DDS7_QUERYINTERFACE             equ     000h
DDS7_ADDREF                     equ     004h
DDS7_RELEASE                    equ     008h
DDS7_ADDATTACHEDSURFACE         equ     00Ch
DDS7_ADDOVERLAYDIRTYRECT        equ     010h
DDS7_BLT                        equ     014h
DDS7_BLTBATCH                   equ     018h
DDS7_BLTFAST                    equ     01Ch
DDS7_DELETEATTACHEDSURFACE      equ     020h
DDS7_ENUMATTACHEDSURFACE        equ     024h
DDS7_ENUMOVERLAYZORDERS         equ     028h
DDS7_FLIP                       equ     02Ch
DDS7_GETATTACHEDSURFACE         equ     030h
DDS7_GETBLTSTATUS               equ     034h
DDS7_GETCAPS                    equ     038h
DDS7_GETCLIPPER                 equ     03Ch
DDS7_GETCOLORKEY                equ     040h
DDS7_GETDC                      equ     044h
DDS7_GETFLIPSTATUS              equ     048h
DDS7_GETOVERLAYPOSITION         equ     04Ch
DDS7_GETPALETTE                 equ     050h
DDS7_GETPIXELFORMAT             equ     054h
DDS7_GETSURFACEDESC             equ     058h
DDS7_INITIALIZE                 equ     05Ch
DDS7_ISLOST                     equ     060h
DDS7_LOCK                       equ     064h
DDS7_RELEASEDC                  equ     068h
DDS7_RESTORE                    equ     06Ch
DDS7_SETCLIPPER                 equ     070h
DDS7_SETCOLORKEY                equ     074h
DDS7_SETOVERLAYPOSITION         equ     078h
DDS7_SETPALETTE                 equ     07Ch
DDS7_UNLOCK                     equ     080h
DDS7_UPDATEOVERLAY              equ     084h
DDS7_UPDATEOVERLAYDISPLAY       equ     088h
DDS7_UPDATEOVERLAYZORDER        equ     08Ch
DDS7_GETDDINTERFACE             equ     090h
DDS7_PAGELOCK                   equ     094h
DDS7_PAGEUNLOCK                 equ     098h
DDS7_SETSURFACEDESC             equ     09Ch
DDS7_SETPRIVATEDATA             equ     100h
DDS7_GETPRIVATEDATA             equ     104h
DDS7_FREEPRIVATEDATA            equ     108h
DDS7_GETUNIQUENESSVALUE         equ     10Ch
DDS7_CHANGEUNIQUENESSVALUE      equ     110h
DDS7_SETPRIORITY                equ     114h
DDS7_GETPRIORITY                equ     118h
DDS7_SETLOD                     equ     11Ch
DDS7_GETLOD                     equ     120h

DDS7_Blt        macro   pt, lparg1, arg2, lparg3, arg4, lparg5
        ifidn ,
                push    NULL
        else
                lea     eax, lparg5
                push    eax
        endif
        mov     eax, arg4
        push    eax
        ifidn ,
                push    NULL
        else
                lea     eax, lparg3
                push    eax
        endif
        mov     eax, arg2
        push    eax
        ifidn ,
                push    NULL
        else
                lea     eax, lparg1
                push    eax
        endif
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_BLT]
                endm

DDS7_BltFast    macro   pt, arg1, arg2, arg3, lparg4, arg5
        mov     eax, arg5
        push    eax
        lea     eax, lparg4
        push    eax
        mov     eax, arg3
        push    eax
        mov     eax, arg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_BLTFAST]
                endm

DDS7_GetAttachedSurface  macro   pt, lparg1, lparg2
        lea     eax, lparg2
        push    eax
        lea     eax, lparg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_GETATTACHEDSURFACE]
                endm
DDS7_GetDC      macro   pt, lparg1
        lea     eax, lparg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_GETDC]
                endm

DDS7_ReleaseDC  macro   pt, arg1
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_RELEASEDC]
                endm

DDS7_Flip       macro   pt, arg1, arg2
        mov     eax, arg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_FLIP]
                endm

DDS7_Restore    macro   pt
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_RESTORE]
                endm

DDS7_Lock       macro   pt, lparg1, lparg2, arg3, arg4
        mov     eax, arg4
        push    eax
        mov     eax, arg3
        push    eax
        lea     eax, lparg2
        push    eax
        ifidn ,
                push    NULL
        else
                lea     eax, lparg1
                push    eax
        endif
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_LOCK]
                endm

DDS7_UnLock     macro   pt, lparg1
        ifidn ,
                push    NULL
        else
                lea     eax, lparg1
                push    eax
        endif
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_UNLOCK]
                endm

DDS7_SetPalette macro   pt, arg1
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_SETPALETTE]
                endm

DDS7_GetPixelFormat macro   pt, lparg1
        ifidn ,
                push    NULL
        else
                lea     eax, lparg1
                push    eax
        endif
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_GETPIXELFORMAT]
                endm

DDS7_SetColorKey macro   pt, arg1, lparg2
        lea     eax, lparg2
        push    eax
        mov     eax, arg1
        push    eax
        mov     eax, pt
        push    eax
        mov     eax, [eax]
        call    dword ptr [eax+DDS7_SETCOLORKEY]
                endm

DDERR_SURFACELOST               equ     0887601C2h
DDERR_WASSTILLDRAWING           equ     08876021Ch

LPDIRECTDRAW7           typedef PTR DWORD
LPDIRECTDRAWSURFACE7    typedef PTR DWORD
LPDIRECTDRAWPALETTE     typedef PTR DWORD

DirectDrawCreateEx      PROTO   :LPGUID,:LPVOID,:REFIID,:IUnknown


dsound.INC

includelib      dsound.lib

DS_QUERYINTERFACE       equ     00h
DS_ADDREF               equ     04h
DS_RELEASE              equ     08h
DS_CREATESOUNDBUFFER    equ     0Ch
DS_GETCAPS              equ     10h
DS_DUPLICATESOUNDBUFFER equ     14h
DS_SETCOOPERATIVELEVEL  equ     18h
DS_COMPACT              equ     1Ch
DS_GETSPEAKERCONFIG     equ     20h
DS_SETSPEAKERCONFIG     equ     24h
DS_INITIALIZE           equ     28h

DSB_QUERYINTERFACE      equ     00h
DSB_ADDREF              equ     04h
DSB_RELEASE             equ     08h
DSB_GETCAPS             equ     0Ch
DSB_GETCURRENTPOSITION  equ     10h
DSB_GETFORMAT           equ     14h
DSB_GETVOLUME           equ     18h
DSB_GETPAN              equ     1Ch
DSB_GETFREQUENCY        equ     20h
DSB_GETSTATUS           equ     24h
DSB_INITIALIZE          equ     28h
DSB_LOCK                equ     2Ch
DSB_PLAY                equ     30h
DSB_SETCURRENTPOSITION  equ     34h
DSB_SETFORMAT           equ     38h
DSB_SETVOLUME           equ     3Ch
DSB_SETPAN              equ     40h
DSB_SETFREQUENCY        equ     44h
DSB_STOP                equ     48h
DSB_UNLOCK              equ     4Ch
DSB_RESTORE             equ     50h

DSC_QUERYINTERFACE      equ     00h
DSC_ADDREF              equ     04h
DSC_RELEASE             equ     08h
DSC_CREATECAPTUREBUFFER equ     0Ch
DSC_GETCAPS             equ     10h
DSC_INITIALIZE          equ     14h

DSCB_QUERYINTERFACE     equ     00h
DSCB_ADDREF             equ     04h
DSCB_RELEASE            equ     08h
DSCB_GETCAPS            equ     0Ch
DSCB_GETCURRENTPOSITION equ     10h
DSCB_GETFORMAT          equ     14h
DSCB_GETSTATUS          equ     18h
DSCB_INITIALIZE         equ     1Ch
DSCB_LOCK               equ     20h
DSCB_START              equ     24h
DSCB_STOP               equ     28h
DSCB_UNLOCK             equ     2Ch

DSN_QUERYINTERFACE              equ     00h
DSN_ADDREF                      equ     04h
DSN_RELEASE                     equ     08h
DSN_SETNOTIFICATIONPOSITION     equ     0Ch

DSBCAPS_PRIMARYBUFFER           equ     0000001h
DSBCAPS_CTRLFREQUENCY           equ     0000020h
DSBCAPS_CTRLVOLUME              equ     0000080h
DSBCAPS_CTRLPOSITIONNOTIFY      equ     0000100h
DSBCAPS_GLOBALFOCUS             equ     0008000h
DSBCAPS_GETCURRENTPOSITION2     equ     0010000h
DSBCAPS_CTRLDEFAULT             equ     00000e0h

DSSCL_NORMAL            equ     00001h
DSSCL_PRIORITY          equ     00002h

DSBUFFERDESC    struct
dwSize          DWORD   ?
dwFlags         DWORD   ?
dwBufferBytes   DWORD   ?
dwReserved      DWORD   ?
lpwfxFormat     LPWAVEFORMATEX  ?
DSBUFFERDESC    ends
LPDSBUFFERDESC  TYPEDEF PTR DSBUFFERDESC

DSBPOSITIONNOTIFY struct
dwOffset        DWORD   ?
hEventNotify    HANDLE  ?
DSBPOSITIONNOTIFY ends
LPDSBPOSITIONNOTIFY TYPEDEF PTR DSbPOSITIONNOTIFY

LPDIRECTSOUND                   TYPEDEF PTR DWORD
LPDIRECTSOUNDCAPTURE            TYPEDEF PTR DWORD
LPDIRECTSOUNDBUFFER             TYPEDEF PTR DWORD
LPDIRECTSOUNDCAPTUREBUFFER      TYPEDEF PTR DWORD
LPDIRECTSOUNDNOTIFY             TYPEDEF PTR DWORD

DirectSoundCreate               PROTO :LPGUID, :LPDIRECTSOUND, :LPVOID
DirectSoundCaptureCreate        PROTO :LPGUID, :LPDIRECTSOUNDCAPTURE, :LPVOID


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.