Cargar recursos en tu juego DirectXLoad resources in your DirectX game

La mayoría de los juegos, en algún momento, cargan recursos y activos (como sombreadores, texturas, mallas predefinidas y otros datos de gráficos) del almacenamiento local u otro flujo de datos.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. Aquí le guiaremos a través de una vista de alto nivel de lo que debe tener en cuenta al cargar estos archivos para usarlos en el juego de Plataforma universal de Windows (UWP) de DirectX C/C++.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.

Por ejemplo, es probable que las mallas para objetos poligonales en el juego estén creadas con otra herramienta y exportadas a un formato específico.For example, the meshes for polygonal objects in your game might have been created with another tool, and exported to a specific format. Lo mismo ocurre con las texturas y otros elementos: aunque un mapa de bits plano no comprimido se pueda escribir con la mayoría de las herramientas y comprender en la mayoría de las API de gráficos, podría resultar extremadamente ineficaz en tu juego.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. A continuación te guiamos en los pasos básicos para cargar tres tipos distintos de recursos gráficos para usar con Direct3D: mallas (modelos), texturas (mapas de bits) y objetos de sombreador compilados.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.

Aspectos que debe saberWhat you need to know

TecnologíasTechnologies

  • Biblioteca de modelos de procesamiento paralelo (ppltasks.h)Parallel Patterns Library (ppltasks.h)

Requisitos previosPrerequisites

  • Comprender Windows Runtime básicoUnderstand the basic Windows Runtime
  • Comprender tareas asincrónicasUnderstand asynchronous tasks
  • Comprender los conceptos básicos de programación de gráficos 3DUnderstand the basic concepts of 3-D graphics programming.

Esta muestra también incluye tres archivos de código para cargar y administrar recursos.This sample also includes three code files for resource loading and management. En este tema, incluimos los objetos de código definidos en estos archivos.You'll encounter the code objects defined in these files throughout this topic.

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

Puedes encontrar el código completo de estas muestras en los siguientes vínculos.The complete code for these samples can be found in the following links.

TemaTopic DescripciónDescription

Código completo para BasicLoaderComplete code for BasicLoader

Código completo de una clase y sus métodos para convertir y cargar objetos de mallas para gráficos en la memoria.Complete code for a class and methods that convert and load graphics mesh objects into memory.

Código completo para BasicReaderWriterComplete code for BasicReaderWriter

Código completo de una clase y sus métodos para leer y escribir archivos de datos binarios en general.Complete code for a class and methods for reading and writing binary data files in general. Usado por la clase BasicLoader.Used by the BasicLoader class.

Código completo para DDSTextureLoaderComplete code for DDSTextureLoader

Código completo de una clase y método para cargar una textura DDS de la memoria.Complete code for a class and method that loads a DDS texture from memory.

 

InstructionsInstructions

Carga asincrónicaAsynchronous loading

La carga asincrónica se controla con la plantilla task de la Biblioteca de modelos de procesamiento paralelo (PPL).Asynchronous loading is handled using the task template from the Parallel Patterns Library (PPL). Una plantilla task contiene una llamada de método seguida por una expresión lambda que procesa los resultados de la llamada asincrónica después de completarse y, normalmente, respeta el siguiente formato: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 });.

Es posible encadenar tareas juntas mediante la sintaxis .then(). Cuando se completa una operación, puede ejecutarse otra operación asincrónica que depende de los resultados de la operación previa.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. De este modo, puedes cargar, convertir y administrar activos complejos en subprocesos independientes, casi invisibles para el jugador.In this way, you can load, convert, and manage complex assets on separate threads in a way that appears almost invisible to the player.

Para más información, consulta Programación asincrónica en C++.For more details, read Asynchronous programming in C++.

Ahora veamos la estructura básica de la declaración y creación de un método asincrónico para cargar archivos, 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;
    });
}

En este código, cuando tu código llama al método ReadDataAsync definido arriba, se crea una tarea para leer un búfer del sistema de archivos.In this code, when your code calls the ReadDataAsync method defined above, a task is created to read a buffer from the file system. Cuando termina, una tarea encadenada toma el búfer y transmite los bytes de ese búfer en una matriz, mediante el tipo DataReader estático.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.          
    });

Esta es la llamada que haces a ReadDataAsync.Here's the call you make to ReadDataAsync. Cuando termina, tu código recibe una matriz de bytes leídos desde el archivo proporcionado.When it completes, your code receives an array of bytes read from the provided file. Dado que ReadDataAsync se define como una tarea, puedes usar una expresión lambda para realizar una operación específica cuando se devuelve la matriz de bytes; por ejemplo, pasar esos datos de bytes a una función DirectX que pueda usarlos.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.

Si tu juego es lo suficientemente simple; carga los recursos con un método como este, cuando el usuario inicie el juego.If your game is sufficiently simple, load your resources with a method like this when the user starts the game. Puedes hacer esto antes de iniciar el bucle principal del juego desde algún punto de la secuencia de llamada de la implementación 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. Nuevamente llamas a los métodos de carga de recursos de manera asincrónica, para que el juego pueda iniciarse más rápido y el jugador no tenga que esperar hasta que termine la carga para sus primeras interacciones.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.

¡Pero no quieres que el juego propiamente dicho empiece con cargas asincrónicas sin completar!However, you don't want to start the game proper until all of the async loading has completed! Crea algún método de señalización de carga completa, como un campo específico, y usa las expresiones lambdas en tus métodos de carga para que se establezca la señal cuando terminen.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. Comprueba la variable antes de iniciar cualquier componente que use esos recursos cargados.Check the variable before starting any components that use those loaded resources.

En este ejemplo, se usan métodos asincrónicos definidos en BasicLoader.cpp para cargar sombreadores, una malla y una textura, cuando se inicia el juego.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. Tenga en cuenta que establece un campo específico en el objeto Game, m _ loadingComplete, cuando finalizan todos los métodos de carga.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.
}

Observa también que las tareas se agregan con el operador && ,como la expresión lambda que establece que la marca de carga completa se desencadene solo cuando finalicen todas las tareas.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. Ten en cuenta que si tienes varias marcas, pueden darse condiciones de carrera.Note that if you have multiple flags, you have the possibility of race conditions. Por ejemplo, cuando la expresión lambda establece dos marcas secuencialmente en el mismo valor, otro subproceso podrá ver solo la primera marca, si las examina antes de establecerse la segunda.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.

Hemos visto cómo cargar archivos de recursos de manera asincrónica.You've seen how to load resource files asynchronously. Las cargas sincrónicas de archivos son mucho más simples. Puedes encontrar ejemplos en Código completo para BasicReaderWriter y Código completo para BasicLoader.Synchronous file loads are much simpler, and you can find examples of them in Complete code for BasicReaderWriter and Complete code for BasicLoader.

Obviamente, distintos tipos de activos y recursos a menudo requieren un procesamiento o conversión adicionales, antes de poder usarse en la canalización de gráficos.Of course, different resource and asset types often require additional processing or conversion before they are ready to be used in your graphics pipeline. Echemos un vistazo a tres tipos específicos de recursos: mallas, texturas y sombreadores.Let's take a look at three specific types of resources: meshes, textures, and shaders.

Cargar mallasLoading meshes

Las mallas son datos de vértices, ya sean generados en procedimientos por el código dentro del juego, o bien exportados a un archivo desde otra aplicación (como 3DStudio MAX o Alias WaveFront) o herramienta.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. Estas mallas representan los modelos del juego; desde primitivos simples, como cubos y esferas, hasta automóviles, casas y caracteres.These meshes represent the models in your game, from simple primitives like cubes and spheres to cars and houses and characters. Por lo general, también tienen color y datos de animación, según su formato.They often contain color and animation data, as well, depending on their format. Nos concentraremos en mallas que contienen solo datos de vértices.We'll focus on meshes that contain only vertex data.

Para cargar una malla de manera correcta, debes conocer el formato de los datos del archivo para la malla.To load a mesh correctly, you must know the format of the data in the file for the mesh. Nuestro tipo BasicReaderWriter anterior simplemente lee los datos como un flujo de bytes. No sabe que los datos de bytes representan una malla, mucho menos detecta un formato específico de malla exportado por otra aplicación.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! Debes realizar las conversiones cuando guardas datos de mallas en la memoria.You must perform the conversion as you bring the mesh data into memory.

(Siempre trata de empaquetar datos de activos en un formato lo más parecido posible a la representación interna.(You should always try to package asset data in a format that's as close to the internal representation as possible. De esta forma, podrás reducir la utilización de recursos y ahorrar tiempo).Doing so will reduce resource utilization and save time.)

Tomemos los datos de bytes del archivo de la malla.Let's get the byte data from the mesh's file. El formato del ejemplo supone que el archivo tiene un formato específico de muestra con el sufijo .vbo.The format in the example assumes that the file is a sample-specific format suffixed with .vbo. (De nuevo, este formato no es el mismo que el formato VBO de OpenGL.) Cada vértice se asigna al tipo BasicVertex, que es una estructura definida en el código de la herramienta de conversión 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. El diseño de los datos de vértices en el archivo .vbo es similar al siguiente:The layout of the vertex data in the .vbo file looks like this:

  • Los primeros 32 bits (4 bytes) del flujo de datos contiene el número de vértices (numVertices) de la malla, representado como un valor 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.
  • Los siguientes 32 bits (4 bytes) del flujo de datos contiene el número de índices (numIndices) de la malla, representado como un valor 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.
  • Después, los bits subsiguientes (numVertices * sizeof (BasicVertex)) contienen los datos del vértice.After that, the subsequent (numVertices * sizeof(BasicVertex)) bits contain the vertex data.
  • Los últimos * bytes (numIndices 16) de datos contienen los datos del índice, que se representan como una secuencia de valores UInt16.The last (numIndices * 16) bits of data contain the index data, represented as a sequence of uint16 values.

Lo importante es que conozcas el diseño del nivel de bits de los datos de la malla que has cargado.The point is this: know the bit-level layout of the mesh data you have loaded. También asegúrate de ser coherente con endianness.Also, be sure you are consistent with endian-ness. Todas las plataformas de Windows 8 son little-endian.All Windows 8 platforms are little-endian.

En el ejemplo, llamas a un método, CreateMesh, a partir del método LoadMeshAsync, para que interprete este nivel de bits.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 los datos de bytes cargados desde el archivo y crea un búfer de vértices y un búfer de índice para la malla pasando las listas de vértices e índices, respectivamente, a ID3D11Device:: CreateBuffer y especificando el búfer de _ vértices de enlace D3D11 _ o el búfer de _ Índice de D3D11 _ _ _ .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. Este código se usa en 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;
    }
}

Normalmente creas un par de búferes de vértices e índices para todas las mallas del juego.You typically create a vertex/index buffer pair for every mesh you use in your game. Puedes elegir dónde y cuándo cargar las mallas.Where and when you load the meshes is up to you. Si tienes varias mallas, quizás quieres cargar algunas del disco en puntos específicos del juego; por ejemplo, durante estados de carga predefinidos específicos. 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. Para mallas grandes, como datos de terreno, puedes transmitir los vértices de una memoria caché; pero este es un procedimiento más complejo y no lo incluimos en este tema.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.

Recuerda que debes conocer el formato de los datos de vértices.Again, know your vertex data format! Hay muchísimas formas de representar datos de vértices en las herramientas que se usan para crear modelos.There are many, many ways to represent vertex data across the tools used to create models. También hay muchas formas de representar el diseño de entrada de los datos de vértices en Direct3D, como franjas y listas de triángulos.There are also many different ways to represent the input layout of the vertex data to Direct3D, such as triangle lists and strips. Para obtener más información sobre los datos de vértices, consulta Introducción a los búferes en Direct3D 11 y Primitivos.For more information about vertex data, read Introduction to Buffers in Direct3D 11 and Primitives.

Echemos un vistazo ahora a la carga de texturas.Next, let's look at loading textures.

Carga de texturasLoading textures

El activo más común en un juego, y el que comprime la mayoría de los archivos en disco y memoria, son las texturas.The most common asset in a game—and the one that comprises most of the files on disk and in memory—are textures. Al igual que las mallas, las texturas pueden tener varios formatos y, cuando las cargas, las conviertes a un formato que Direct3D pueda usar.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. Las texturas también vienen en una amplia variedad de tipos para crear distintos efectos.Textures also come in a wide variety of types and are used to create different effects. Los niveles de MIP para texturas se pueden usar para mejorar la apariencia y el rendimiento de objetos distantes; los mapas de luces y suciedad se usan para efectos de capa y detalles encima de una textura base, y los mapas normales se usan en cálculos de iluminación por píxel.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. En un juego moderno, una escena común puede tener miles de texturas individuales y el código debe administrar a todas de manera efectiva.In a modern game, a typical scene can potentially have thousands of individual textures, and your code must effectively manage them all!

También al igual que las mallas, hay varios formatos específicos para que el uso de la memoria sea más eficaz.Also like meshes, there are a number of specific formats that are used to make memory usage for efficient. Dado que las texturas fácilmente consumen una amplia parte de la memoria de GPU (y del sistema), por lo general se comprimen de alguna forma.Since textures can easily consume a large portion of the GPU (and system) memory, they are often compressed in some fashion. No es necesario que comprimas las texturas del juego. Puedes usar cualquier algoritmo de compresión y descompresión, siempre que proporciones los datos a los sombreadores de Direct3D en un formato que les resulte comprensible (como un mapa de bits 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 proporciona soporte para los algoritmos de compresión de texturas DXT, aunque no todos los formatos DXT son compatibles con el hardware gráfico del jugador.Direct3D provides support for the DXT texture compression algorithms, although every DXT format may not be supported in the player's graphics hardware. Los archivos DDS contienen texturas DXT (y otros formatos de compresión de texturas) y llevan el sufijo .dds.DDS files contain DXT textures (and other texture compression formats as well), and are suffixed with .dds.

Un archivo DDS es un archivo binario que contiene la siguiente información:A DDS file is a binary file that contains the following information:

  • Un DWORD (número mágico) con el valor de código de cuatro caracteres 'DDS ' (0x20534444).A DWORD (magic number) containing the four character code value 'DDS ' (0x20534444).

  • Una descripción de los datos del archivo.A description of the data in the file.

    Los datos se describen con una descripción de encabezado mediante el ** _ encabezado DDS**; el formato de píxel se define mediante los píxeles de **DDS _ **.The data is described with a header description using DDS_HEADER; the pixel format is defined using DDS_PIXELFORMAT. Tenga en cuenta que el ** _ encabezado DDS** y las estructuras de ** _ PIXELFORMAT de DDS** reemplazan las estructuras DDSURFACEDESC2, DDSCAPS2 y DDPIXELFORMAT de DirectDraw 7 desusadas.Note that the DDS_HEADER and DDS_PIXELFORMAT structures replace the deprecated DDSURFACEDESC2, DDSCAPS2 and DDPIXELFORMAT DirectDraw 7 structures. DDS _ HEADER es el equivalente binario de DDSURFACEDESC2 y DDSCAPS2.DDS_HEADER is the binary equivalent of DDSURFACEDESC2 and DDSCAPS2. DDS _ PIXELFORMAT es el equivalente binario de DDPIXELFORMAT.DDS_PIXELFORMAT is the binary equivalent of DDPIXELFORMAT.

    DWORD               dwMagic;
    DDS_HEADER          header;
    

    Si el valor de dwFlags en DDS _ PIXELFORMAT se establece en DDPF _ FourCC y dwFourCC se establece en "contenido DX10", se presentará una estructura de encabezado DDS adicional _ _ DXT10 para acomodar las matrices de textura o los formatos de DXGI que no se pueden expresar como un formato de píxel RGB, como los formatos de punto flotante, los formatos sRGB, etc. Cuando la estructura _ _ DXT10 del encabezado DDS esté presente, toda la descripción de los datos tendrá el siguiente aspecto.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;
    
  • Puntero a una matriz de bytes que contiene los datos de la superficie principal.A pointer to an array of bytes that contains the main surface data.

    BYTE bdata[]
    
  • Puntero a una matriz de bytes que contiene las superficies restantes, como los niveles de mapas MIP, caras de un mapa de cubos, profundidades de una textura de volumen.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. Sigue estos vínculos para obtener más información sobre el diseño de un archivo DDS para: una textura, un mapa de cubos o una textura de volumen.Follow these links for more information about the DDS file layout for a: texture, a cube map, or a volume texture.

    BYTE bdata2[]
    

Muchas herramientas exportan al formato DDS.Many tools export to the DDS format. Si no tienes una herramienta que exporte la textura a este formato, piensa en crear una.If you don't have a tool to export your texture to this format, consider creating one. Para más información sobre el formato DDS y cómo usarlo en tu código, consulta Guía de programación para DDS.For more detail on the DDS format and how to work with it in your code, read Programming Guide for DDS. En nuestro ejemplo, usaremos DDS.In our example, we'll use DDS.

Al igual que con otros tipos de recursos, lees los datos de un archivo como un flujo de bytes.As with other resource types, you read the data from a file as a stream of bytes. Cuando termina la tarea de carga, la llamada a lambda ejecuta código (el método CreateTexture) para procesar el flujo de bytes en un formato que Direct3D pueda usar.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
            );
    });
}

En el fragmento de código anterior, la expresión lambda comprueba si el nombre de archivo tiene una extensión de "dds".In the previous snippet, the lambda checks to see if the filename has an extension of "dds". Si la tiene, supones que es una textura DDS.If it does, you assume that it is a DDS texture. De lo contrario, usa las API de Windows Imaging Component (WIC) para saber cuál es el formato y decodificar los datos como un mapa de bits.If not, well, use the Windows Imaging Component (WIC) APIs to discover the format and decode the data as a bitmap. De una u otra forma, el resultado será un mapa de bits Texture2D (o un error).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();
    }
}

Cuando este código se completa, tienes Texture2D en la memoria, cargada desde un archivo de imagen.When this code completes, you have a Texture2D in memory, loaded from an image file. Al igual que con las mallas, probablemente tengas varias texturas en el juego y en una escena determinada.As with meshes, you probably have a lot of them in your game and in any given scene. Piensa en crear memorias caché para texturas de acceso frecuente por escena o por nivel, en lugar de cargarlas a todas cuando el juego o nivel se inician.Consider creating caches for regularly accessed textures per-scene or per-level, rather than loading them all when the game or level starts.

(Puedes analizar con mayor profundidad el método CreateDDSTextureFromMemory llamado en la muestra anterior, en Código completo para DDSTextureLoader).(The CreateDDSTextureFromMemory method called in the above sample can be explored in full in Complete code for DDSTextureLoader.)

También pueden asignarse texturas individuales o "máscaras" de texturas a superficies o polígonos de mallas específicos.Also, individual textures or texture "skins" may map to specific mesh polygons or surfaces. Estos datos de asignación generalmente se exportan con la herramienta que el intérprete o el diseñador usaron para crear el modelo y las texturas.This mapping data is usually exported by the tool an artist or designer used to create the model and the textures. Asegúrate de capturar también esta información cuando cargues los datos exportados, porque la necesitarás para asignar las texturas correctas a las superficies correspondientes cuando realices el sombreado de fragmento.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.

Cargar sombreadoresLoading shaders

Los sombreadores son archivos de lenguaje de sombreado de alto nivel (HLSL) compilados, que se cargan en la memoria y se invocan en etapas específicas de la canalización de gráficos.Shaders are compiled High Level Shader Language (HLSL) files that are loaded into memory and invoked at specific stages of the graphics pipeline. Los sombreadores más comunes y esenciales son los de vértices y píxeles, que procesan los vértices individuales de la malla y los píxeles de la ventanilla de la escena, respectivamente.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. El código HLSL se ejecuta para transformar la geometría, aplicar texturas y efectos de iluminación, y realizar un procesamiento posterior en la escena representada.The HLSL code is executed to transform the geometry, apply lighting effects and textures, and perform post-processing on the rendered scene.

Un juego Direct3D puede tener distintos sombreadores, cada uno compilado en un archivo CSO (Compiled Shader Object, .cso) independiente.A Direct3D game can have a number of different shaders, each one compiled into a separate CSO (Compiled Shader Object, .cso) file. Por lo general, no hay tantos que necesites cargar dinámicamente y, en la mayoría de los casos, puedes cargarlos cuando se inicia el juego o por nivel (como un sombreador para efectos de lluvia).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).

El código de la clase BasicLoader proporciona una cantidad de sobrecargas para distintos sombreadores, incluidos los de casco, píxeles, geometría y vértices.The code in the BasicLoader class provides a number of overloads for different shaders, including vertex, geometry, pixel, and hull shaders. El siguiente código incluye sombreadores de píxeles como ejemplo.The code below covers pixel shaders as an example. (Encontrarás el código completo en Código completo para 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);
    });
}

En este ejemplo, se usa la instancia de BasicReaderWriter (m _ BasicReaderWriter) para leer en el archivo objeto de sombreador compilado (. CSO) proporcionado como un flujo de bytes.In this example, you use the BasicReaderWriter instance (m_basicReaderWriter) to read in the supplied compiled shader object (.cso) file as a byte stream. Cuando la tarea termina, la expresión lambda llama a ID3D11Device::CreatePixelShader con los datos de bytes cargados desde el archivo.Once that task completes, the lambda calls ID3D11Device::CreatePixelShader with the byte data loaded from the file. La devolución de llamada debe establecer una marca que indique que la carga se realizó correctamente y el código debe comprobar esta marca antes de ejecutar el sombreador.Your callback must set some flag indicating that the load was successful, and your code must check this flag before running the shader.

Los sombreadores de vértices son un poco más complejos.Vertex shaders are bit more complex. Para un sombreador de vértices, también cargas un diseño de entrada independiente que define los datos de vértices.For a vertex shader, you also load a separate input layout that defines the vertex data. El siguiente código se usa para cargar asincrónicamente un sombreador de vértices junto con un diseño de entrada de vértices personalizado.The following code can be used to asynchronously load a vertex shader along with a custom vertex input layout. Asegúrate de que la información de vértices que cargas desde las mallas pueda representarse en este diseño de entrada de manera correcta.Be sure that the vertex information that you load from your meshes can be correctly represented by this input layout!

Creemos el diseño de entrada antes de cargar el sombreador de vértices.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);
    }
}

En este diseño particular, cada vértice tiene los siguientes datos procesados por el sombreador de vértices:In this particular layout, each vertex has the following data processed by the vertex shader:

  • Una posición de coordenadas 3D (x, y, z) en el espacio de coordenadas del modelo, representada como un trío de valores de punto flotante de 32 bits.A 3D coordinate position (x, y, z) in the model's coordinate space, represented as a trio of 32-bit floating point values.
  • Un vector normal para el vértice, también representado como tres valores de punto flotante de 32 bits.A normal vector for the vertex, also represented as three 32-bit floating point values.
  • Un valor de coordenada para la textura 2D transformada (u, v), representado como un par de valores flotantes de 32 bits.A transformed 2D texture coordinate value (u, v) , represented as a pair of 32-bit floating values.

Estos elementos de entrada por vértice se denominan semántica de HLSL. Son un conjunto de registros definidos que se usa para pasar datos desde y hacia el objeto de sombreador compilado.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 canalización ejecuta el sombreador de vértices una vez para cada vértice en la malla que cargaste.Your pipeline runs the vertex shader once for every vertex in the mesh that you've loaded. La semántica define la entrada al sombreador de vértices (y la salida de él) a medida que se ejecuta y proporciona estos datos para los cálculos por vértice en el código HLSL del sombreador.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.

Ahora carga el objeto del sombreador de vértices.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);   
        }
    });
}

En este código, después de leer los datos de bytes para el archivo CSO del sombreador de vértices, creas el sombreador de vértices llamando a 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. A continuación, creas el diseño de entrada para el sombreador en la misma expresión lambda.After that, you create your input layout for the shader in the same lambda.

Otros tipos de sombreador, como los sombreadores de casco y geometría, también pueden requerir una configuración específica.Other shader types, such as hull and geometry shaders, can also require specific configuration. Encontrarás el código completo para una variedad de métodos de carga de sombreadores en Código completo para BasicLoader y en Muestra de carga de recursos de Direct3D.Complete code for a variety of shader loading methods is provided in Complete code for BasicLoader and in the Direct3D resource loading sample.

ObservacionesRemarks

Llegados a este punto, deberías comprender y poder crear o modificar métodos para cargar asincrónicamente activos y recursos comunes para juegos, como mallas, texturas y sombreadores compilados.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.