Caricare risorse nel gioco DirectXLoad resources in your DirectX game

Nella maggior parte dei giochi vengono in determinati momenti caricati asset e risorse, quali shader, trame, mesh o altri dati grafici, dallo spazio di archiviazione locale o da altri flussi di dati.Most games, at some point, load resources and assets (such as shaders, textures, predefined meshes or other graphics data) from local storage or some other data stream. In questo articolo viene fornita una panoramica di alto livello degli elementi che è necessario prendere in considerazione durante il caricamento di questi file da usare nel gioco DirectX C/C++ piattaforma UWP (Universal Windows Platform) (UWP).Here, we walk you through a high-level view of what you must consider when loading these files to use in your DirectX C/C++ Universal Windows Platform (UWP) game.

Le mesh per gli oggetti poligonali del gioco potrebbero ad esempio essere state create con un altro strumento ed esportate in un formato specifico.For example, the meshes for polygonal objects in your game might have been created with another tool, and exported to a specific format. Lo stesso, a maggior ragione, vale per le trame: sebbene le immagini bitmap non compresse in formato flat possano essere in genere scritte da molti strumenti e riconosciute dalla maggior parte delle API, esse possono rivelarsi estremamente inefficienti per l'uso in un gioco.The same is true for textures, and more so: while a flat, uncompressed bitmap can be commonly written by most tools and understood by most graphics APIs, it can be extremely inefficient for use in your game. In questo argomento vengono descritti i passaggi di base per caricare tre diversi tipi di risorse grafiche per l'uso con Direct3D: mesh (modelli), trame (bitmap) e oggetti shader compilati.Here, we guide you through the basic steps for loading three different types of graphic resources for use with Direct3D: meshes (models), textures (bitmaps), and compiled shader objects.

Informazioni importantiWhat you need to know

TecnologieTechnologies

  • Libreria PPL (Parallel Patterns Library, ppltasks.h)Parallel Patterns Library (ppltasks.h)

PrerequisitiPrerequisites

  • Informazioni di base su Windows RuntimeUnderstand the basic Windows Runtime
  • Informazioni sulle attività asincroneUnderstand asynchronous tasks
  • Informazioni sui concetti di base della programmazione di grafica 3DUnderstand the basic concepts of 3-D graphics programming.

Questo esempio include inoltre tre file di codice per il caricamento e la gestione delle risorse.This sample also includes three code files for resource loading and management. Agli oggetti di codice definiti in questi file viene fatto riferimento nell'intero argomento.You'll encounter the code objects defined in these files throughout this topic.

  • BasicLoader.h/.cppBasicLoader.h/.cpp
  • BasicReaderWriter.h/.cppBasicReaderWriter.h/.cpp
  • DDSTextureLoader.h/.cppDDSTextureLoader.h/.cpp

Il codice completo di questi esempi è disponibile visitando i collegamenti seguenti.The complete code for these samples can be found in the following links.

ArgomentoTopic DescrizioneDescription

Codice completo per BasicLoaderComplete code for BasicLoader

Codice completo per una classe e i metodi per la conversione e il caricamento degli oggetti mesh grafici nella memoria.Complete code for a class and methods that convert and load graphics mesh objects into memory.

Codice completo per BasicReaderWriterComplete code for BasicReaderWriter

Codice completo per una classe e i metodi di lettura e scrittura di file di dati binari in generale.Complete code for a class and methods for reading and writing binary data files in general. Usato dalla classe BasicLoader.Used by the BasicLoader class.

Codice completo per DDSTextureLoaderComplete code for DDSTextureLoader

Codice completo per una classe e un metodo di caricamento di una trama DDS dalla memoria.Complete code for a class and method that loads a DDS texture from memory.

 

IstruzioniInstructions

Caricamento asincronoAsynchronous loading

Il caricamento asincrono viene gestito mediante il modello di task della libreria PPL (Parallel Patterns Library).Asynchronous loading is handled using the task template from the Parallel Patterns Library (PPL). Un oggetto task contiene una chiamata di metodo seguita da una funzione lambda che elabora i risultati della chiamata asincrona dopo che questa viene completata. Il formato usato in genere è:A task contains a method call followed by a lambda that processes the results of the async call after it completes, and usually follows the format of:

task<generic return type>(async code to execute).then((parameters for lambda){ lambda code contents });.task<generic return type>(async code to execute).then((parameters for lambda){ lambda code contents });.

Le attività possono essere concatenate mediante la sintassi di .then() in modo che al termine di un'operazione possa essere eseguita un'altra operazione asincrona dipendente dai risultati di quella precedente.Tasks can be chained together using the .then() syntax, so that when one operation completes, another async operation that depends on the results of the prior operation can be run. Pertanto, è possibile caricare, convertire e gestire risorse complesse in thread separati in un modo quasi invisibile al giocatore.In this way, you can load, convert, and manage complex assets on separate threads in a way that appears almost invisible to the player.

Per altre info, vedi la pagina relativa alla programmazione asincrona in C++.For more details, read Asynchronous programming in C++.

Esaminiamo ora la struttura di base per dichiarare e creare un metodo di caricamento file asincrono, ReadDataAsync.Now, let's look at the basic structure for declaring and creating an async file loading method, ReadDataAsync.

#include <ppltasks.h>

// ...
concurrency::task<Platform::Array<byte>^> ReadDataAsync(
        _In_ Platform::String^ filename);

// ...

using concurrency;

task<Platform::Array<byte>^> BasicReaderWriter::ReadDataAsync(
    _In_ Platform::String^ filename
    )
{
    return task<StorageFile^>(m_location->GetFileAsync(filename)).then([=](StorageFile^ file)
    {
        return FileIO::ReadBufferAsync(file);
    }).then([=](IBuffer^ buffer)
    {
        auto fileData = ref new Platform::Array<byte>(buffer->Length);
        DataReader::FromBuffer(buffer)->ReadBytes(fileData);
        return fileData;
    });
}

Quando in questo codice viene chiamato il metodo ReadDataAsync definito sopra, viene creato un task di lettura di un buffer del sistema.In this code, when your code calls the ReadDataAsync method defined above, a task is created to read a buffer from the file system. Al termine, un task concatenato acquisisce il buffer e ne trasmette i byte in un flusso a una matrice con il tipo statico DataReader.Once it completes, a chained task takes the buffer and streams the bytes from that buffer into an array using the static DataReader type.

m_basicReaderWriter = ref new BasicReaderWriter();

// ...
return m_basicReaderWriter->ReadDataAsync(filename).then([=](const Platform::Array<byte>^ bytecode)
    {
      // Perform some operation with the data when the async load completes.          
    });

Ecco la chiamata effettuata a ReadDataAsync.Here's the call you make to ReadDataAsync. Al termine, il codice riceve una matrice di byte letti dal file specificato.When it completes, your code receives an array of bytes read from the provided file. Poiché il metodo ReadDataAsync stesso è definito come task, possiamo usare una funzione lambda per eseguire un'operazione specifica quando viene restituita la matrice di byte, ad esempio il passaggio dei dati in byte a una funzione DirectX in grado di utilizzarli.Since ReadDataAsync itself is defined as a task, you can use a lambda to perform a specific operation when the byte array is returned, such as passing that byte data to a DirectX function that can use it.

Se il gioco è sufficientemente semplice, possiamo caricare le risorse con un metodo di questo tipo all'avvio del gioco.If your game is sufficiently simple, load your resources with a method like this when the user starts the game. Questa operazione può essere eseguita prima dell'avvio del loop di gioco principale in un qualche punto della sequenza di chiamata dell'implementazione di IFrameworkView::Run.You can do this before you start the main game loop from some point in the call sequence of your IFrameworkView::Run implementation. Come è stato già accennato, possiamo chiamare i metodi di caricamento delle risorse in modo asincrono così che non sia necessario attendere che il caricamento venga completato per poter intraprendere altre interazioni.Again, you call your resource loading methods asynchronously so the game can start quicker and so the player doesn't have to wait until the loading completes before engaging in early interactions.

Tuttavia, potremmo volere che il gioco vero e proprio non inizi finché tutte le operazioni di caricamento asincrono non siano state completate.However, you don't want to start the game proper until all of the async loading has completed! A tale scopo, crea un metodo per segnalare il completamento del download, ad esempio un campo specifico, e usa le funzioni lambda nei metodi di caricamento per impostare tale segnale al completamento dell'operazione.Create some method for signaling when loading is complete, such as a specific field, and use the lambdas on your loading method(s) to set that signal when finished. Controlla la variabile prima di avviare componenti che usano le risorse caricate.Check the variable before starting any components that use those loaded resources.

Ecco un esempio di uso di metodi asincroni definiti in BasicLoader.cpp per caricare shader, una mesh e una trama all'avvio del gioco.Here's an example using the async methods defined in BasicLoader.cpp to load shaders, a mesh, and a texture when the game starts up. Si noti che imposta un campo specifico nell'oggetto Game, m _ loadingComplete, al termine di tutti i metodi di caricamento.Notice that it sets a specific field on the game object, m_loadingComplete, when all of the loading methods finish.

void ResourceLoading::CreateDeviceResources()
{
    // DirectXBase is a common sample class that implements a basic view provider. 
    
    DirectXBase::CreateDeviceResources(); 

    // ...

    // This flag will keep track of whether or not all application
    // resources have been loaded.  Until all resources are loaded,
    // only the sample overlay will be drawn on the screen.
    m_loadingComplete = false;

    // Create a BasicLoader, and use it to asynchronously load all
    // application resources.  When an output value becomes non-null,
    // this indicates that the asynchronous operation has completed.
    BasicLoader^ loader = ref new BasicLoader(m_d3dDevice.Get());

    auto loadVertexShaderTask = loader->LoadShaderAsync(
        "SimpleVertexShader.cso",
        nullptr,
        0,
        &m_vertexShader,
        &m_inputLayout
        );

    auto loadPixelShaderTask = loader->LoadShaderAsync(
        "SimplePixelShader.cso",
        &m_pixelShader
        );

    auto loadTextureTask = loader->LoadTextureAsync(
        "reftexture.dds",
        nullptr,
        &m_textureSRV
        );

    auto loadMeshTask = loader->LoadMeshAsync(
        "refmesh.vbo",
        &m_vertexBuffer,
        &m_indexBuffer,
        nullptr,
        &m_indexCount
        );

    // The && operator can be used to create a single task that represents
    // a group of multiple tasks. The new task's completed handler will only
    // be called once all associated tasks have completed. In this case, the
    // new task represents a task to load various assets from the package.
    (loadVertexShaderTask && loadPixelShaderTask && loadTextureTask && loadMeshTask).then([=]()
    {
        m_loadingComplete = true;
    });

    // Create constant buffers and other graphics device-specific resources here.
}

Ricorda che i task sono stati aggregati mediante l'operatore &&amp; in modo tale che la funzione lambda che imposta il flag di completamento del caricamento venga attivata solo al termine di tutti i task.Note that the tasks have been aggregated using the && operator such that the lambda that sets the loading complete flag is triggered only when all of the tasks complete. Tieni presente che se ci sono più flag, possono verificarsi race condition.Note that if you have multiple flags, you have the possibility of race conditions. Se ad esempio la funzione lambda imposta due flag in sequenza sullo stesso valore, un altro thread può rilevare solo il primo flag impostato esaminandolo prima che venga impostato il secondo.For example, if the lambda sets two flags sequentially to the same value, another thread may only see the first flag set if it examines them before the second flag is set.

Abbiamo visto come caricare file di risorse in modo asincrono.You've seen how to load resource files asynchronously. Il caricamento sincrono di file è notevolmente più semplice. Puoi trovarne esempi nel codice completo per BasicReaderWriter e nel codice completo per BasicLoader.Synchronous file loads are much simpler, and you can find examples of them in Complete code for BasicReaderWriter and Complete code for BasicLoader.

Naturalmente, a seconda dei tipi di risorse e di asset è spesso possibile che siano necessarie ulteriori operazioni di elaborazione e conversione affinché tali elementi risultino utilizzabili nella pipeline grafica.Of course, different resource and asset types often require additional processing or conversion before they are ready to be used in your graphics pipeline. Esaminiamo tre tipi specifici di risorse, le mesh, le trame e gli shader.Let's take a look at three specific types of resources: meshes, textures, and shaders.

Caricamento di meshLoading meshes

Le mesh sono dati sui vertici, generati a livello procedurale dal codice del gioco oppure esportati in un file da un'altra app (ad esempio 3DStudio MAX o Alias WaveFront) o da un altro strumento.Meshes are vertex data, either generated procedurally by code within your game or exported to a file from another app (like 3DStudio MAX or Alias WaveFront) or tool. Le mesh rappresentano i modelli del gioco, dalle semplici primitive come cubi e sfere fino a veicoli, edifici e personaggi.These meshes represent the models in your game, from simple primitives like cubes and spheres to cars and houses and characters. A seconda del formato, spesso contengono anche dati su colori e animazioni.They often contain color and animation data, as well, depending on their format. In questo argomento ci occuperemo delle mesh contenenti solo dati sui vertici.We'll focus on meshes that contain only vertex data.

Per caricare correttamente una mesh, devi conoscere il formato dei dati contenuti nel relativo file.To load a mesh correctly, you must know the format of the data in the file for the mesh. Il semplice tipo BasicReaderWriter descritto sopra legge i dati solo come flusso di byte senza rilevare che essi rappresentano una mesh né tanto meno un formato di mesh specifico esportato da un'altra applicazione.Our simple BasicReaderWriter type above simply reads the data in as a byte stream; it doesn't know that the byte data represents a mesh, much less a specific mesh format as exported by another application! La conversione deve essere eseguita durante il trasferimento dei dati della mesh in memoria.You must perform the conversion as you bring the mesh data into memory.

È consigliabile provare sempre a creare pacchetti di dati degli asset in un formato il più vicino possibile alla rappresentazione interna.(You should always try to package asset data in a format that's as close to the internal representation as possible. In questo modo, è possibile ridurre l'utilizzo delle risorse e risparmiare tempo.Doing so will reduce resource utilization and save time.)

Recuperiamo i dati in byte dal file della mesh.Let's get the byte data from the mesh's file. In questo caso si presuppone che il formato del file sia specifico dell'esempio e presenti il suffisso vbo.The format in the example assumes that the file is a sample-specific format suffixed with .vbo. Ricorda che non si tratta del formato VBO OpenGL. Ogni vertice è mappato al tipo BasicVertex, il quale è uno struct definito nel codice per lo strumento di conversione obj2vbo.(Again, this format is not the same as OpenGL's VBO format.) Each vertex itself maps to the BasicVertex type, which is a struct defined in the code for the obj2vbo converter tool. Il layout dei dati sui vertici nel file vbo è analogo al seguente:The layout of the vertex data in the .vbo file looks like this:

  • I primi 32 bit (4 byte) del flusso di dati contengono il numero dei vertici (numVertices) della mesh, rappresentato come valore uint32.The first 32 bits (4 bytes) of the data stream contain the number of vertices (numVertices) in the mesh, represented as a uint32 value.
  • I 32 bit (4 byte) successivi del flusso di dati contengono il numero di indici della mesh (numIndices) rappresentato come valore uint32.The next 32 bits (4 bytes) of the data stream contain the number of indices in the mesh (numIndices), represented as a uint32 value.
  • Successivamente, i bit successivi (numVertices * sizeof (BasicVertex)) contengono i dati dei vertici.After that, the subsequent (numVertices * sizeof(BasicVertex)) bits contain the vertex data.
  • Gli ultimi (numIndices * 16) bit di dati contengono i dati dell'indice, rappresentati come sequenza di valori UInt16.The last (numIndices * 16) bits of data contain the index data, represented as a sequence of uint16 values.

L'aspetto essenziale è conoscere il layout a livello di bit dei dati della mesh caricata.The point is this: know the bit-level layout of the mesh data you have loaded. Assicurati inoltre di mantenere la coerenza con la codifica endian.Also, be sure you are consistent with endian-ness. Tutte le piattaforme Windows 8 sono provviste di codifica Little Endian.All Windows 8 platforms are little-endian.

Nell'esempio viene chiamato il metodo CreateMesh dal metodo LoadMeshAsync per eseguire questa interpretazione a livello di bit.In the example, you call a method, CreateMesh, from the LoadMeshAsync method to perform this bit-level interpretation.

task<void> BasicLoader::LoadMeshAsync(
    _In_ Platform::String^ filename,
    _Out_ ID3D11Buffer** vertexBuffer,
    _Out_ ID3D11Buffer** indexBuffer,
    _Out_opt_ uint32* vertexCount,
    _Out_opt_ uint32* indexCount
    )
{
    return m_basicReaderWriter->ReadDataAsync(filename).then([=](const Platform::Array<byte>^ meshData)
    {
        CreateMesh(
            meshData->Data,
            vertexBuffer,
            indexBuffer,
            vertexCount,
            indexCount,
            filename
            );
    });
}

CreateMesh interpreta i dati di byte caricati dal file e crea un buffer di vertici e un buffer di indice per la mesh passando rispettivamente gli elenchi di vertici e di indice a ID3D11Device:: CreateBuffer e specificando d3d11 _ binding _ Vertex _ buffer o d3d11 _ Binding index buffer _ _ .CreateMesh interprets the byte data loaded from the file, and creates a vertex buffer and an index buffer for the mesh by passing the vertex and index lists, respectively, to ID3D11Device::CreateBuffer and specifying either D3D11_BIND_VERTEX_BUFFER or D3D11_BIND_INDEX_BUFFER. Ecco il codice usato in BasicLoader:Here's the code used in BasicLoader:

void BasicLoader::CreateMesh(
    _In_ byte* meshData,
    _Out_ ID3D11Buffer** vertexBuffer,
    _Out_ ID3D11Buffer** indexBuffer,
    _Out_opt_ uint32* vertexCount,
    _Out_opt_ uint32* indexCount,
    _In_opt_ Platform::String^ debugName
    )
{
    // The first 4 bytes of the BasicMesh format define the number of vertices in the mesh.
    uint32 numVertices = *reinterpret_cast<uint32*>(meshData);

    // The following 4 bytes define the number of indices in the mesh.
    uint32 numIndices = *reinterpret_cast<uint32*>(meshData + sizeof(uint32));

    // The next segment of the BasicMesh format contains the vertices of the mesh.
    BasicVertex* vertices = reinterpret_cast<BasicVertex*>(meshData + sizeof(uint32) * 2);

    // The last segment of the BasicMesh format contains the indices of the mesh.
    uint16* indices = reinterpret_cast<uint16*>(meshData + sizeof(uint32) * 2 + sizeof(BasicVertex) * numVertices);

    // Create the vertex and index buffers with the mesh data.

    D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
    vertexBufferData.pSysMem = vertices;
    vertexBufferData.SysMemPitch = 0;
    vertexBufferData.SysMemSlicePitch = 0;
    CD3D11_BUFFER_DESC vertexBufferDesc(numVertices * sizeof(BasicVertex), D3D11_BIND_VERTEX_BUFFER);

    m_d3dDevice->CreateBuffer(
            &vertexBufferDesc,
            &vertexBufferData,
            vertexBuffer
            );
    
    D3D11_SUBRESOURCE_DATA indexBufferData = {0};
    indexBufferData.pSysMem = indices;
    indexBufferData.SysMemPitch = 0;
    indexBufferData.SysMemSlicePitch = 0;
    CD3D11_BUFFER_DESC indexBufferDesc(numIndices * sizeof(uint16), D3D11_BIND_INDEX_BUFFER);
    
    m_d3dDevice->CreateBuffer(
            &indexBufferDesc,
            &indexBufferData,
            indexBuffer
            );
  
    if (vertexCount != nullptr)
    {
        *vertexCount = numVertices;
    }
    if (indexCount != nullptr)
    {
        *indexCount = numIndices;
    }
}

In genere viene creata una coppia di vertex buffer e index buffer per ogni mesh usata nel gioco.You typically create a vertex/index buffer pair for every mesh you use in your game. Sei tu a scegliere dove e quando vengono caricare le mesh.Where and when you load the meshes is up to you. Se disponi di molte mesh, potrebbe essere preferibile caricarne alcune dal disco in momenti specifici del gioco, ad esempio durante determinati stati di caricamento predefiniti.If you have a lot of meshes, you may only want to load some from the disk at specific points in the game, such as during specific, pre-defined loading states. Per le mesh di grandi dimensioni, ad esempio i dati sul terreno, è possibile trasmettere i vertici in un flusso da una cache, tuttavia questo tipo di procedura è più complesso ed esula dall'ambito di questo argomento.For large meshes, like terrain data, you can stream the vertices from a cache, but that is a more complex procedure and not in the scope of this topic.

È quindi effettivamente importante conoscere il formato dei dati sui vertici.Again, know your vertex data format! Esistono numerosi modi di rappresentare i dati sui vertici negli strumenti usati per creare i modelli.There are many, many ways to represent vertex data across the tools used to create models. Esistono anche diversi modi di rappresentare il layout di input dei dati sui vertici in Direct3D, ad esempio come strisce ed elenchi di triangoli.There are also many different ways to represent the input layout of the vertex data to Direct3D, such as triangle lists and strips. Per altre informazioni sui dati sui vertici, leggi gli argomenti relativi all'introduzione ai buffer in Direct3D 11 e alle primitive.For more information about vertex data, read Introduction to Buffers in Direct3D 11 and Primitives.

Esaminiamo ora il caricamento delle trame.Next, let's look at loading textures.

Caricamento delle trameLoading textures

L'asset più comune in un gioco, ossia quello che costituisce la maggior parte dei file su disco e in memoria, è rappresentato dalle trame.The most common asset in a game—and the one that comprises most of the files on disk and in memory—are textures. Analogamente alle mesh, le trame possono presentarsi in diversi formati convertibili in un formato compatibile con Direct3D al momento del caricamento.Like meshes, textures can come in a variety of formats, and you convert them to a format that Direct3D can use when you load them. Anche le trame possono essere di diversi tipi e vengono usate per creare vari effetti.Textures also come in a wide variety of types and are used to create different effects. È possibile usare i livelli MIP per le trame allo scopo di migliorare l'aspetto e le prestazioni di oggetti distanza; le mappe di effetti di sporco o di illuminazione consentono di creare effetti su livelli e dettagli della trama di base. Le mappe normali vengono usate nei calcoli per l'illuminazione pixel per pixel.MIP levels for textures can be used to improve the look and performance of distance objects; dirt and light maps are used to layer effects and detail atop a base texture; and normal maps are used in per-pixel lighting calculations. In un gioco moderno, una scena tipica può essere potenzialmente costituita da migliaia di singole trame e il codice deve essere in grado di gestirle tutte in modo efficiente.In a modern game, a typical scene can potentially have thousands of individual textures, and your code must effectively manage them all!

Come per le mesh, esistono numerosi formati specifici usati per rendere più efficiente l'uso della memoria.Also like meshes, there are a number of specific formats that are used to make memory usage for efficient. Poiché le trame possono facilmente esaurire una parte notevole della memoria della GPU (e del sistema) spesso vengono compresse in qualche modo.Since textures can easily consume a large portion of the GPU (and system) memory, they are often compressed in some fashion. L'uso della compressione per le trame di un gioco non è obbligatorio ed è possibile avvalersi di qualsiasi algoritmo di compressione/decompressione desiderato a condizione di fornire agli shader Direct3D dati in un formato riconoscibile (ad esempio una bitmap Texture2D).You aren't required to use compression on your game's textures, and you can use any compression/decompression algorithm(s) you want as long as you provide the Direct3D shaders with data in a format it can understand (like a Texture2D bitmap).

Direct3D offre supporto per gli algoritmi di compressione delle trame DXT, sebbene alcuni formati DXT potrebbero non essere supportati nell'hardware grafico del lettore.Direct3D provides support for the DXT texture compression algorithms, although every DXT format may not be supported in the player's graphics hardware. I file DDS contengono trame DXT (e altri formati di compressione delle trame) e presentano il suffisso dds.DDS files contain DXT textures (and other texture compression formats as well), and are suffixed with .dds.

Un file DDS è un file binario contenente le informazioni seguenti:A DDS file is a binary file that contains the following information:

  • Un valore DWORD (numero chiave) contenente il valore del codice di quattro caratteri 'DDS ' (0x20534444).A DWORD (magic number) containing the four character code value 'DDS ' (0x20534444).

  • Una descrizione dei dati nel file.A description of the data in the file.

    I dati vengono descritti con una descrizione dell'intestazione usando l' ** _ intestazione DDS**. il formato pixel viene definito usando DDS _ PIXELFORMAT.The data is described with a header description using DDS_HEADER; the pixel format is defined using DDS_PIXELFORMAT. Si noti che l' _ intestazione DDS e le strutture DDS _ PIXELFORMAT sostituiscono le strutture deprecate DDSURFACEDESC2, DDSCAPS2 e DDPIXELFORMAT di DirectDraw 7.Note that the DDS_HEADER and DDS_PIXELFORMAT structures replace the deprecated DDSURFACEDESC2, DDSCAPS2 and DDPIXELFORMAT DirectDraw 7 structures. DDS _ HEADER è l'equivalente binario di DDSURFACEDESC2 e DDSCAPS2.DDS_HEADER is the binary equivalent of DDSURFACEDESC2 and DDSCAPS2. DDS _ PIXELFORMAT è l'equivalente binario di DDPIXELFORMAT.DDS_PIXELFORMAT is the binary equivalent of DDPIXELFORMAT.

    DWORD               dwMagic;
    DDS_HEADER          header;
    

    Se il valore di dwFlags in DDS _ PIXELFORMAT è impostato su DDPF _ fourcc e dwFourCC è impostato su "DX10", sarà presente un'intestazione DDS aggiuntiva struttura ** _ _ DXT10** per contenere matrici di trama o formati DXGI che non possono essere espressi come formato pixel RGB, ad esempio formati a virgola mobile, formati sRGB e così via. Quando è presente la struttura dell' ** _ intestazione DDS _ DXT10** , l'intera descrizione dei dati sarà simile alla seguente.If the value of dwFlags in DDS_PIXELFORMAT is set to DDPF_FOURCC and dwFourCC is set to "DX10" an additional DDS_HEADER_DXT10 structure will be present to accommodate texture arrays or DXGI formats that cannot be expressed as an RGB pixel format such as floating point formats, sRGB formats etc. When the DDS_HEADER_DXT10 structure is present, the entire data description will looks like this.

    DWORD               dwMagic;
    DDS_HEADER          header;
    DDS_HEADER_DXT10    header10;
    
  • Un puntatore a una matrice di byte contenente i dati della superficie principale.A pointer to an array of bytes that contains the main surface data.

    BYTE bdata[]
    
  • Un puntatore a una matrice di byte contenente le superfici rimanenti, ad esempio i livelli mipmap, le facce in una mappa di cubo, le profondità in una trama di volume.A pointer to an array of bytes that contains the remaining surfaces such as; mipmap levels, faces in a cube map, depths in a volume texture. Visita questi collegamenti per altre informazioni sul layout dei file DDS per una texture, una mappa di cubo o una trama di volume.Follow these links for more information about the DDS file layout for a: texture, a cube map, or a volume texture.

    BYTE bdata2[]
    

Esistono molti strumenti che consentono l'esportazione in formato DDS.Many tools export to the DDS format. Se non disponi di uno strumento per esportare la trama in questo formato, prova a crearne uno.If you don't have a tool to export your texture to this format, consider creating one. Per ulteriori dettagli sul formato DDS e su come usarlo nel codice, leggi la Guida alla programmazione per DDS.For more detail on the DDS format and how to work with it in your code, read Programming Guide for DDS. In questo esempio, usiamo DDS.In our example, we'll use DDS.

Come con altri tipi di risorse, i dati vengono letti da un file come flusso di byte.As with other resource types, you read the data from a file as a stream of bytes. Al termine dell'attività di caricamento, la chiamata lambda esegue il codice (il metodo CreateTexture) per elaborare il flusso di byte in un formato utilizzabile da Direct3D.Once your loading task completes, the lambda call runs code (the CreateTexture method) to process the stream of bytes into a format that Direct3D can use.

task<void> BasicLoader::LoadTextureAsync(
    _In_ Platform::String^ filename,
    _Out_opt_ ID3D11Texture2D** texture,
    _Out_opt_ ID3D11ShaderResourceView** textureView
    )
{
    return m_basicReaderWriter->ReadDataAsync(filename).then([=](const Platform::Array<byte>^ textureData)
    {
        CreateTexture(
            GetExtension(filename) == "dds",
            textureData->Data,
            textureData->Length,
            texture,
            textureView,
            filename
            );
    });
}

Nel frammento di codice precedente la funzione lambda verifica che il nome del file abbia estensione "dds".In the previous snippet, the lambda checks to see if the filename has an extension of "dds". In caso affermativo, presuppone che si tratti di una trama DDS.If it does, you assume that it is a DDS texture. In caso contrario, verranno usate le API del Componente Windows Imaging per individuare il formato e decodificare i dati come bitmap.If not, well, use the Windows Imaging Component (WIC) APIs to discover the format and decode the data as a bitmap. In un caso o nell'altro, il risultato è una bitmap Texture2D (o un errore).Either way, the result is a Texture2D bitmap (or an error).

void BasicLoader::CreateTexture(
    _In_ bool decodeAsDDS,
    _In_reads_bytes_(dataSize) byte* data,
    _In_ uint32 dataSize,
    _Out_opt_ ID3D11Texture2D** texture,
    _Out_opt_ ID3D11ShaderResourceView** textureView,
    _In_opt_ Platform::String^ debugName
    )
{
    ComPtr<ID3D11ShaderResourceView> shaderResourceView;
    ComPtr<ID3D11Texture2D> texture2D;

    if (decodeAsDDS)
    {
        ComPtr<ID3D11Resource> resource;

        if (textureView == nullptr)
        {
            CreateDDSTextureFromMemory(
                m_d3dDevice.Get(),
                data,
                dataSize,
                &resource,
                nullptr
                );
        }
        else
        {
            CreateDDSTextureFromMemory(
                m_d3dDevice.Get(),
                data,
                dataSize,
                &resource,
                &shaderResourceView
                );
        }

        resource.As(&texture2D);
    }
    else
    {
        if (m_wicFactory.Get() == nullptr)
        {
            // A WIC factory object is required in order to load texture
            // assets stored in non-DDS formats.  If BasicLoader was not
            // initialized with one, create one as needed.
            CoCreateInstance(
                    CLSID_WICImagingFactory,
                    nullptr,
                    CLSCTX_INPROC_SERVER,
                    IID_PPV_ARGS(&m_wicFactory));
        }

        ComPtr<IWICStream> stream;
        m_wicFactory->CreateStream(&stream);

        stream->InitializeFromMemory(
                data,
                dataSize);

        ComPtr<IWICBitmapDecoder> bitmapDecoder;
        m_wicFactory->CreateDecoderFromStream(
                stream.Get(),
                nullptr,
                WICDecodeMetadataCacheOnDemand,
                &bitmapDecoder);

        ComPtr<IWICBitmapFrameDecode> bitmapFrame;
        bitmapDecoder->GetFrame(0, &bitmapFrame);

        ComPtr<IWICFormatConverter> formatConverter;
        m_wicFactory->CreateFormatConverter(&formatConverter);

        formatConverter->Initialize(
                bitmapFrame.Get(),
                GUID_WICPixelFormat32bppPBGRA,
                WICBitmapDitherTypeNone,
                nullptr,
                0.0,
                WICBitmapPaletteTypeCustom);

        uint32 width;
        uint32 height;
        bitmapFrame->GetSize(&width, &height);

        std::unique_ptr<byte[]> bitmapPixels(new byte[width * height * 4]);
        formatConverter->CopyPixels(
                nullptr,
                width * 4,
                width * height * 4,
                bitmapPixels.get());

        D3D11_SUBRESOURCE_DATA initialData;
        ZeroMemory(&initialData, sizeof(initialData));
        initialData.pSysMem = bitmapPixels.get();
        initialData.SysMemPitch = width * 4;
        initialData.SysMemSlicePitch = 0;

        CD3D11_TEXTURE2D_DESC textureDesc(
            DXGI_FORMAT_B8G8R8A8_UNORM,
            width,
            height,
            1,
            1
            );

        m_d3dDevice->CreateTexture2D(
                &textureDesc,
                &initialData,
                &texture2D);

        if (textureView != nullptr)
        {
            CD3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc(
                texture2D.Get(),
                D3D11_SRV_DIMENSION_TEXTURE2D
                );

            m_d3dDevice->CreateShaderResourceView(
                    texture2D.Get(),
                    &shaderResourceViewDesc,
                    &shaderResourceView);
        }
    }


    if (texture != nullptr)
    {
        *texture = texture2D.Detach();
    }
    if (textureView != nullptr)
    {
        *textureView = shaderResourceView.Detach();
    }
}

Al termine di questo codice, è necessario un Texture2D in memoria, caricato da un file di immagine.When this code completes, you have a Texture2D in memory, loaded from an image file. Come per le mesh, è probabile che il gioco contenga molte trame in ogni scena.As with meshes, you probably have a lot of them in your game and in any given scene. Prova a creare cache per le trame a cui si accede di frequente per singola scena o per singolo livello, piuttosto che caricarle tutte all'avvio del gioco o del livello.Consider creating caches for regularly accessed textures per-scene or per-level, rather than loading them all when the game or level starts.

Il metodo CreateDDSTextureFromMemory chiamato nell'esempio precedente può essere esaminato in dettaglio nel codice completo per DDSTextureLoader.(The CreateDDSTextureFromMemory method called in the above sample can be explored in full in Complete code for DDSTextureLoader.)

Inoltre, singole trame o "skin" di trame possono essere mappate a superfici o poligoni di mesh specifici.Also, individual textures or texture "skins" may map to specific mesh polygons or surfaces. Questi dati di mapping vengono in genere esportati dallo strumento usato dall'artista o progettista per creare il modello e le trame.This mapping data is usually exported by the tool an artist or designer used to create the model and the textures. Accertati di acquisire queste informazioni anche quando carichi i dati esportati in quanto verranno usate per mappare le trame corrette alle superfici corrispondenti al momento di eseguire lo shading dei frammenti.Make sure that you capture this information as well when you load the exported data, as you will use it map the correct textures to the corresponding surfaces when you perform fragment shading.

Caricamento di shaderLoading shaders

Gli shader sono file HLSL (High Level Shader Language) compilati che vengono caricati in memoria e richiamati in fasi specifiche della pipeline di grafica.Shaders are compiled High Level Shader Language (HLSL) files that are loaded into memory and invoked at specific stages of the graphics pipeline. Gli shader più comuni ed essenziali sono i vertex shader e i pixel shader, i quali elaborano rispettivamente i singoli vertici della mesh e i pixel nei riquadri della scena.The most common and essential shaders are the vertex and pixel shaders, which process the individual vertices of your mesh and the pixels in the scene's viewport(s), respectively. Il codice HLSL viene eseguito per trasformare la geometria, applicare trame ed effetti di illuminazione ed eseguire la post-elaborazione della scena di cui è stato eseguito il rendering.The HLSL code is executed to transform the geometry, apply lighting effects and textures, and perform post-processing on the rendered scene.

Un gioco Direct3D può presentare numerosi shader diversi, ognuno dei quali compilato in un file CSO (Compiled Shader Object, .cso) distinto.A Direct3D game can have a number of different shaders, each one compiled into a separate CSO (Compiled Shader Object, .cso) file. In genere, non ne sono presenti tanti da doverne predisporre il caricamento dinamico e nella maggior parte dei casi è possibile caricarli semplicemente all'avvio del gioco, o dei singoli livelli (ad esempio uno shader per effetti di pioggia).Normally, you don't have so many that you need to load them dynamically, and in most cases, you can simply load them when the game is starting, or on a per-level basis (such as a shader for rain effects).

Il codice nella classe BasicLoader offre numerosi overload per diversi shader, ad esempio vertex shader, pixel shader, hull shader e geometry shader.The code in the BasicLoader class provides a number of overloads for different shaders, including vertex, geometry, pixel, and hull shaders. Nel codice seguente vengono considerati a titolo di esempio i pixel shader.The code below covers pixel shaders as an example. Puoi esaminare il codice completo in Codice completo per BasicLoader.(You can review the complete code in Complete code for BasicLoader.)

concurrency::task<void> LoadShaderAsync(
    _In_ Platform::String^ filename,
    _Out_ ID3D11PixelShader** shader
    );

// ...

task<void> BasicLoader::LoadShaderAsync(
    _In_ Platform::String^ filename,
    _Out_ ID3D11PixelShader** shader
    )
{
    return m_basicReaderWriter->ReadDataAsync(filename).then([=](const Platform::Array<byte>^ bytecode)
    {
        
       m_d3dDevice->CreatePixelShader(
                bytecode->Data,
                bytecode->Length,
                nullptr,
                shader);
    });
}

In questo esempio si usa l'istanza di BasicReaderWriter (m _ BasicReaderWriter) per leggere il file dell'oggetto shader compilato (. CSO) fornito come flusso di byte.In this example, you use the BasicReaderWriter instance (m_basicReaderWriter) to read in the supplied compiled shader object (.cso) file as a byte stream. Al termine dell'attività, la funzione lambda chiama ID3D11Device::CreatePixelShader con i dati in byte caricati dal file.Once that task completes, the lambda calls ID3D11Device::CreatePixelShader with the byte data loaded from the file. Il callback deve impostare un flag indicante l'esito positivo del caricamento il quale deve essere verificato dal codice per poter eseguire lo shader.Your callback must set some flag indicating that the load was successful, and your code must check this flag before running the shader.

Per i vertex shader il discorso è più complesso.Vertex shaders are bit more complex. Per i vertex shader, si carica un layout di input distinto che definisce i dati sui vertici.For a vertex shader, you also load a separate input layout that defines the vertex data. Il codice seguente può essere usato per caricare in modo asincrono un vertex shader insieme a un layout di input del vertice personalizzato.The following code can be used to asynchronously load a vertex shader along with a custom vertex input layout. Accertati che le informazioni sui vertici caricate dalle mesh possano essere rappresentate in modo corretto da questo layout di input.Be sure that the vertex information that you load from your meshes can be correctly represented by this input layout!

Creiamo il layout di input prima di caricare il vertex shader.Let's create the input layout before you load the vertex shader.

void BasicLoader::CreateInputLayout(
    _In_reads_bytes_(bytecodeSize) byte* bytecode,
    _In_ uint32 bytecodeSize,
    _In_reads_opt_(layoutDescNumElements) D3D11_INPUT_ELEMENT_DESC* layoutDesc,
    _In_ uint32 layoutDescNumElements,
    _Out_ ID3D11InputLayout** layout
    )
{
    if (layoutDesc == nullptr)
    {
        // If no input layout is specified, use the BasicVertex layout.
        const D3D11_INPUT_ELEMENT_DESC basicVertexLayoutDesc[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        };

        m_d3dDevice->CreateInputLayout(
                basicVertexLayoutDesc,
                ARRAYSIZE(basicVertexLayoutDesc),
                bytecode,
                bytecodeSize,
                layout);
    }
    else
    {
        m_d3dDevice->CreateInputLayout(
                layoutDesc,
                layoutDescNumElements,
                bytecode,
                bytecodeSize,
                layout);
    }
}

In questo specifico layout, per ogni vertice il vertex shader elabora i dati seguenti:In this particular layout, each vertex has the following data processed by the vertex shader:

  • Una posizione nelle coordinate 3D (x, y, z) dello spazio di coordinate del modello, rappresentata come tre valori a virgola mobile a 32 bit.A 3D coordinate position (x, y, z) in the model's coordinate space, represented as a trio of 32-bit floating point values.
  • Un vettore normale per il vertice, anche rappresentato come tre valori a virgola mobile a 32 bit.A normal vector for the vertex, also represented as three 32-bit floating point values.
  • Un valore di coordinate (u, v) della trama 2D trasformata, rappresentato come coppia di valori a virgola mobile a 32 bit.A transformed 2D texture coordinate value (u, v) , represented as a pair of 32-bit floating values.

Questi elementi di input per vertice costituiscono la semantica HLSL e si tratta di un set di registri definiti usati per scambiare dati con l'oggetto shader compilato.These per-vertex input elements are called HLSL semantics, and they are a set of defined registers used to pass data to and from your compiled shader object. La pipeline esegue il vertex shader una volta per ogni vertice nella mesh caricata.Your pipeline runs the vertex shader once for every vertex in the mesh that you've loaded. Durante la sua esecuzione, la semantica definisce l'input al (e l'output dal) vertex shader e fornisce i dati per i calcoli per vertice nel codice HLSL dello shader.The semantics define the input to (and output from) the vertex shader as it runs, and provide this data for your per-vertex computations in your shader's HLSL code.

Carichiamo quindi l'oggetto vertex shader.Now, load the vertex shader object.

concurrency::task<void> LoadShaderAsync(
        _In_ Platform::String^ filename,
        _In_reads_opt_(layoutDescNumElements) D3D11_INPUT_ELEMENT_DESC layoutDesc[],
        _In_ uint32 layoutDescNumElements,
        _Out_ ID3D11VertexShader** shader,
        _Out_opt_ ID3D11InputLayout** layout
        );

// ...

task<void> BasicLoader::LoadShaderAsync(
    _In_ Platform::String^ filename,
    _In_reads_opt_(layoutDescNumElements) D3D11_INPUT_ELEMENT_DESC layoutDesc[],
    _In_ uint32 layoutDescNumElements,
    _Out_ ID3D11VertexShader** shader,
    _Out_opt_ ID3D11InputLayout** layout
    )
{
    // This method assumes that the lifetime of input arguments may be shorter
    // than the duration of this task.  In order to ensure accurate results, a
    // copy of all arguments passed by pointer must be made.  The method then
    // ensures that the lifetime of the copied data exceeds that of the task.

    // Create copies of the layoutDesc array as well as the SemanticName strings,
    // both of which are pointers to data whose lifetimes may be shorter than that
    // of this method's task.
    shared_ptr<vector<D3D11_INPUT_ELEMENT_DESC>> layoutDescCopy;
    shared_ptr<vector<string>> layoutDescSemanticNamesCopy;
    if (layoutDesc != nullptr)
    {
        layoutDescCopy.reset(
            new vector<D3D11_INPUT_ELEMENT_DESC>(
                layoutDesc,
                layoutDesc + layoutDescNumElements
                )
            );

        layoutDescSemanticNamesCopy.reset(
            new vector<string>(layoutDescNumElements)
            );

        for (uint32 i = 0; i < layoutDescNumElements; i++)
        {
            layoutDescSemanticNamesCopy->at(i).assign(layoutDesc[i].SemanticName);
        }
    }

    return m_basicReaderWriter->ReadDataAsync(filename).then([=](const Platform::Array<byte>^ bytecode)
    {
       m_d3dDevice->CreateVertexShader(
                bytecode->Data,
                bytecode->Length,
                nullptr,
                shader);

        if (layout != nullptr)
        {
            if (layoutDesc != nullptr)
            {
                // Reassign the SemanticName elements of the layoutDesc array copy to point
                // to the corresponding copied strings. Performing the assignment inside the
                // lambda body ensures that the lambda will take a reference to the shared_ptr
                // that holds the data.  This will guarantee that the data is still valid when
                // CreateInputLayout is called.
                for (uint32 i = 0; i < layoutDescNumElements; i++)
                {
                    layoutDescCopy->at(i).SemanticName = layoutDescSemanticNamesCopy->at(i).c_str();
                }
            }

            CreateInputLayout(
                bytecode->Data,
                bytecode->Length,
                layoutDesc == nullptr ? nullptr : layoutDescCopy->data(),
                layoutDescNumElements,
                layout);   
        }
    });
}

In questo codice, dopo aver letto i dati in byte per il file CSO del vertex shader, è possibile creare il vertex shader chiamando ID3D11Device:: CreateVertexShader.In this code, once you've read in the byte data for the vertex shader's CSO file, you create the vertex shader by calling ID3D11Device::CreateVertexShader. Al termine, creiamo il layout di input per lo shader nella stessa funzione lambda.After that, you create your input layout for the shader in the same lambda.

Altri tipi di shader, ad esempio gli hull shader e i geometry shader, possono anche richiedere configurazioni specifiche.Other shader types, such as hull and geometry shaders, can also require specific configuration. Il codice completo per diversi metodi di caricamento degli shader è disponibile nel codice completo per BasicLoader e nell'esempio di caricamento di risorse Direct3D.Complete code for a variety of shader loading methods is provided in Complete code for BasicLoader and in the Direct3D resource loading sample.

CommentiRemarks

Abbiamo a questo punto esaminato i concetti e siamo in grado di creare o modificare metodi per il caricamento asincrono di risorse e asset comuni dei giochi, quali mesh, trame e shader compilati.At this point, you should understand and be able to create or modify methods for asynchronously loading common game resources and assets, such as meshes, textures, and compiled shaders.