Carregar recursos no jogo em DirectXLoad resources in your DirectX game

A maioria dos jogos, em algum momento, carrega recursos e ativos (por exemplo, sombreadores, texturas, malhas predefinidas ou outros dados gráficos) do armazenamento local ou de algum outro fluxo de dados.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. Aqui, vamos examinar uma exibição de alto nível daquilo que é preciso considerar ao carregar esses arquivos para uso no jogo DirectX C/C++ da UWP (Plataforma Universal do Windows).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 exemplo, é possível que as malhas para objetos poligonais do jogo tenham sido criadas com outra ferramenta e exportadas para um 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. O mesmo é verdadeiro para texturas e muito mais, por isso: embora um bitmap descompactado possa ser rotineiramente criado pela maioria das ferramentas e entendido por grande parte das APIs gráficas, esse bitmap pode ser extremamente ineficiente se usado no jogo.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. Nesta seção, vamos guiá-lo pelas etapas básicas do carregamento de três tipos diferentes de recursos gráficos para uso com o Direct3D: malhas (modelos), texturas (bitmaps) e 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.

O que você precisa saberWhat you need to know

TecnologiasTechnologies

  • Padrão PPL (ppltasks.h)Parallel Patterns Library (ppltasks.h)

Pré-requisitosPrerequisites

  • Entender os princípios básicos do Windows RuntimeUnderstand the basic Windows Runtime
  • Entender tarefas assíncronasUnderstand asynchronous tasks
  • Entenda os conceitos básicos de programação de gráficos 3D.Understand the basic concepts of 3-D graphics programming.

Esse exemplo também inclui três arquivos de código para carregamento e gerenciamento de recursos.This sample also includes three code files for resource loading and management. Você encontrará o objetos de código definidos nesses arquivos, neste tópico.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

O código completo desses exemplos pode ser encontrado nos seguintes links.The complete code for these samples can be found in the following links.

TópicoTopic DescriçãoDescription

Concluir código para BasicLoaderComplete code for BasicLoader

Conclua o código para classe e métodos que convertam e carreguem objetos da malha de elementos gráficos na memória.Complete code for a class and methods that convert and load graphics mesh objects into memory.

Concluir código para BasicReaderWriterComplete code for BasicReaderWriter

Conclua código para classe e métodos de leitura e gravação de arquivos de dados binários em geral.Complete code for a class and methods for reading and writing binary data files in general. Usado pela classe BasicLoader.Used by the BasicLoader class.

Concluir código para DDSTextureLoaderComplete code for DDSTextureLoader

Conclua o código para a classe e o método que carrega uma textura DDS da memória.Complete code for a class and method that loads a DDS texture from memory.

 

InstruçõesInstructions

Carregamento assíncronoAsynchronous loading

O carregamento assíncrono é feito com o modelo task do padrão PPL.Asynchronous loading is handled using the task template from the Parallel Patterns Library (PPL). A task contém uma chamada de método seguida por um lambda que processa os resultados da chamada assíncrona depois de concluída e, normalmente, segue o formato de: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 });.

As tarefas podem ser encadeadas juntas usando a sintaxe .then(), portanto, quando uma operação é concluída, outra operação assíncrona que dependa dos resultados da operação anterior pode ser executada.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. Dessa forma, é possível carregar, converter e gerenciar ativos complexos em threads separados, de maneira que pareçam quase invisíveis para o jogador.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 obter mais informações, consulte Programação assíncrona em C++.For more details, read Asynchronous programming in C++.

Agora, vamos examinar a estrutura básica de declaração e criação de um método de carregamento de arquivo assíncrono, 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;
    });
}

Nesse código, quando o seu código chama o método ReadDataAsync definido acima, uma tarefa é criada para ler um buffer no sistema de arquivos.In this code, when your code calls the ReadDataAsync method defined above, a task is created to read a buffer from the file system. Concluída essa etapa, uma tarefa encadeada obtém o buffer e distribui o fluxo de bytes desse buffer em uma matriz, usando o 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 é a chamada que você faz para ReadDataAsync.Here's the call you make to ReadDataAsync. Depois de concluída, o seu código recebe uma matriz dos bytes lidos no arquivo fornecido.When it completes, your code receives an array of bytes read from the provided file. Como o próprio ReadDataAsync é definido como uma tarefa, é possível usar um lambda para executar uma operação específica quando a matriz de bytes é retornada; por exemplo, passar esses dados de bytes para uma função DirectX que possa usá-los.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 o jogo for suficientemente simples, use um método como esse para carregar os recursos quando o usuário iniciar o jogo.If your game is sufficiently simple, load your resources with a method like this when the user starts the game. Faça isso antes de iniciar o loop de jogo principal a partir de algum ponto na sequência de chamada da sua implementação 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. Novamente, você chama os métodos de carregamento de recursos de maneira assíncrona para que o jogo possa ser iniciado com mais rapidez e para que o jogador não precise esperar a conclusão do carregamento para, então, se envolver nas primeiras interações.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.

Contudo, você quer que o jogo comece somente quando todo o carregamento assíncrono estiver concluído.However, you don't want to start the game proper until all of the async loading has completed! Crie algum método de sinalização para a conclusão do carregamento (por exemplo, um campo específico) e use o lambda no(s) método(s) de carregamento para definir esse sinal, quando finalizado.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. Verifique a variável antes de iniciar qualquer componente que utilize os recursos carregados.Check the variable before starting any components that use those loaded resources.

Aqui está um exemplo que usa os métodos assíncronos definidos em BasicLoader.cpp para carregar sombreadores, uma malha e uma textura já na inicialização do jogo.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. Observe que ele define um campo específico no objeto Game, m _ loadingComplete, quando todos os métodos de carregamento são concluídos.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.
}

Observe também que as tarefas foram agregadas usando o operador &&, de modo que o lambda que define o sinalizador de conclusão do carregamento é acionado apenas quando todas as tarefas são concluídas.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. Lembre-se: se houver vários sinalizadores, poderão ocorrer condições de corrida.Note that if you have multiple flags, you have the possibility of race conditions. Por exemplo, se o lambda definir dois sinalizadores sequencialmente para o mesmo valor, outro thread poderá ver apenas o primeiro sinalizador, caso ele o examine antes de o segundo finalizador ser definido.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.

Você viu como carregar arquivos de recursos de maneira assíncrona.You've seen how to load resource files asynchronously. Os carregamentos síncronos de arquivos são muito mais simples, e você pode encontrar exemplos em Concluir código para BasicReaderWriter e em Concluir código 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.

Claro!, diferentes tipos de recursos e ativos geralmente exigem processamento adicional ou conversão antes de estarem prontos para uso no seu pipeline gráfico.Of course, different resource and asset types often require additional processing or conversion before they are ready to be used in your graphics pipeline. Vamos examinar três tipos específicos de recursos: malhas, texturas e sombreadores.Let's take a look at three specific types of resources: meshes, textures, and shaders.

Carregando malhasLoading meshes

Malhas são dados de vértice, gerados via procedimentos pelo código no jogo ou exportados de um arquivo de outro aplicativo (como 3DStudio MAX ou Alias WaveFront) ou ferramenta.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. Essas malhas representam os modelos do jogo, desde primitivos simples, como cubos e esferas, até carros e casas e caracteres.These meshes represent the models in your game, from simple primitives like cubes and spheres to cars and houses and characters. Elas geralmente contêm dados de cor e animação, dependendo do formato.They often contain color and animation data, as well, depending on their format. Vamos nos concentrar nas malhas que contêm apenas dados de vértice.We'll focus on meshes that contain only vertex data.

Para carregar corretamente uma malha, é preciso conhecer o formato dos dados no arquivo da malha.To load a mesh correctly, you must know the format of the data in the file for the mesh. Nosso tipo simples BasicReaderWriter, acima, lê os dados como um fluxo de bytes; ele não sabe que os dados de bytes representam uma malha, muito menos que um formato de malha específico foi exportado por outro aplicativo.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! Você precisa executar a conversão quando traz os dados de malha para a memória.You must perform the conversion as you bring the mesh data into memory.

(Tente sempre empacotar dados de ativos em um formato o mais próximo possível da representação interna.(You should always try to package asset data in a format that's as close to the internal representation as possible. Fazendo assim, você reduzirá a utilização de recursos e economizará tempo.)Doing so will reduce resource utilization and save time.)

Vamos extrair os dados de bytes do arquivo da malha.Let's get the byte data from the mesh's file. O formato do exemplo pressupõe que o arquivo está em um formato específico do exemplo, com sufixo .vbo.The format in the example assumes that the file is a sample-specific format suffixed with .vbo. (Novamente, esse formato não é igual ao formato VBO do OpenGL.) Cada vértice em si é mapeado para o tipo BasicVertex, que é uma estrutura definida no código para a ferramenta de conversão 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. O layout dos dados de vértice no arquivo .vbo se parece com o seguinte:The layout of the vertex data in the .vbo file looks like this:

  • Os primeiros 32 bits (4 bytes) do fluxo de dados contêm o número de vértices (numVertices) da malha, representado como um 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.
  • Os primeiros 32 bits (4 bytes) do fluxo de dados contêm o número de índices (numIndices) da malha, representado como um 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.
  • Depois disso, os bits subsequentes (numVertices * sizeof (BasicVertex)) contêm os dados de vértice.After that, the subsequent (numVertices * sizeof(BasicVertex)) bits contain the vertex data.
  • Os últimos (numIndices * 16) bits de dados contêm os dados de índice, representados como uma sequência de valores UInt16.The last (numIndices * 16) bits of data contain the index data, represented as a sequence of uint16 values.

O ponto é este: saber o layout no nível de bit dos dados de malha carregados.The point is this: know the bit-level layout of the mesh data you have loaded. Além disso, verifique se há consistência com endian-ness.Also, be sure you are consistent with endian-ness. Todas as plataformas Windows 8 são little-endian.All Windows 8 platforms are little-endian.

No exemplo, você chama um método CreateMesh no método LoadMeshAsync para executar essa interpretação no nível do 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 os dados de byte carregados do arquivo e cria um buffer de vértice e um buffer de índice para a malha, passando as listas de vértices e de índice, respectivamente, para ID3D11Device:: CreateBuffer e especificando o _ buffer de vértice de associação D3D11 ou o buffer de índice de associação 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. Aqui está o código usado em 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;
    }
}

Em geral, você cria um par de buffers de vértices/índices para cada malha usada no jogo.You typically create a vertex/index buffer pair for every mesh you use in your game. Você é quem decide onde e quando carregar as malhas.Where and when you load the meshes is up to you. Quando há muitas malhas, pode ser conveniente carregar apenas algumas delas no disco, em pontos específicos do jogo; por exemplo, durante estados de carregamento específicos e predefinidos.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 malhas grandes, como dados de terreno, você pode distribuir o fluxo de vértices de um cache, mas este é um procedimento mais complexo e não faz parte do escopo deste tópico.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.

Enfatizando mais uma vez: conheça o formato dos seus dados de vértice.Again, know your vertex data format! Há inúmeras formas de representar dados de vértice nas ferramentas usadas para criar modelos.There are many, many ways to represent vertex data across the tools used to create models. Também há muitas maneiras diferentes de representar o layout de entrada dos dados de vértice para Direct3D; por exemplo, listas de triângulos e faixas.There are also many different ways to represent the input layout of the vertex data to Direct3D, such as triangle lists and strips. Para saber mais sobre dados de vértice, leia Introdução aos buffers em Direct3D 11 e Primitivos.For more information about vertex data, read Introduction to Buffers in Direct3D 11 and Primitives.

A seguir, vamos examinar o carregamento de texturas.Next, let's look at loading textures.

Carregar texturasLoading textures

O ativo mais comum de um jogo, e aquele que abrange a maioria dos arquivos em disco e memória, são as texturas.The most common asset in a game—and the one that comprises most of the files on disk and in memory—are textures. Assim como as malhas, as texturas podem vir em vários formatos; então, você os converte em um formato que o Direct3D possa usar quando carregá-las.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. Texturas também têm uma grande variedade de tipos e são usadas para criar efeitos distintos.Textures also come in a wide variety of types and are used to create different effects. Os níveis MIP de texturas podem ser usados para melhorar a aparência e o desempenho de objetos de distância; mapas de entulho e luzes são usados para efeitos de camada e detalhes sobre a textura base; e mapas normais são usados em cálculos de iluminação por 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. Em um jogo moderno, um cenário típico pode ter milhares de texturas individuais e o seu código deve efetivamente gerenciar todas elas.In a modern game, a typical scene can potentially have thousands of individual textures, and your code must effectively manage them all!

Também como nas malhas, há muitos formatos específicos que são usados para tornar eficiente o uso da memória.Also like meshes, there are a number of specific formats that are used to make memory usage for efficient. Como as texturas podem consumir facilmente uma grande parte da memória GPU (e de sistema), elas são compactadas de alguma forma.Since textures can easily consume a large portion of the GPU (and system) memory, they are often compressed in some fashion. Não há necessidade de usar compactação nas texturas do seu jogo, e você pode usar qualquer algoritmo de compactação/descompactação que quiser, desde que forneça os sombreadores Direct3D com dados em um formato que ele possa entender (como um 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).

O Direct3D dá suporte a algoritmos de compactação de textura DXT, embora nem todo formato DXT dê suporte ao hardware gráfico do jogador.Direct3D provides support for the DXT texture compression algorithms, although every DXT format may not be supported in the player's graphics hardware. Arquivos DDS contêm texturas DXT (e também outros formatos de compactação de textura) e usam o sufixo .dds.DDS files contain DXT textures (and other texture compression formats as well), and are suffixed with .dds.

Um arquivo DDS é um arquivo binário que contém as seguintes informações:A DDS file is a binary file that contains the following information:

  • Um DWORD (número mágico) contendo o valor de código com quatro caracteres 'DDS' (0x20534444).A DWORD (magic number) containing the four character code value 'DDS ' (0x20534444).

  • Uma descrição dos dados no arquivo.A description of the data in the file.

    Os dados são descritos com uma descrição de cabeçalho usando o ** _ cabeçalho DDS**; o formato de pixel é definido usando o DDS _ PIXELFORMAT.The data is described with a header description using DDS_HEADER; the pixel format is defined using DDS_PIXELFORMAT. Observe que as estruturas do ** _ cabeçalho DDS** e do DDS _ PIXELFORMAT substituem as estruturas preteridas DDSURFACEDESC2, DDSCAPS2 e DDPIXELFORMAT DirectDraw 7.Note that the DDS_HEADER and DDS_PIXELFORMAT structures replace the deprecated DDSURFACEDESC2, DDSCAPS2 and DDPIXELFORMAT DirectDraw 7 structures. DDS _ HEADER é o equivalente binário de DDSURFACEDESC2 e DDSCAPS2.DDS_HEADER is the binary equivalent of DDSURFACEDESC2 and DDSCAPS2. DDS _ PIXELFORMAT é o equivalente binário de DDPIXELFORMAT.DDS_PIXELFORMAT is the binary equivalent of DDPIXELFORMAT.

    DWORD               dwMagic;
    DDS_HEADER          header;
    

    Se o valor de dwFlags em DDS _ PIXELFORMAT for definido como DDPF _ FOURCC e dwFourCC for definido como "DX10", uma estrutura de ** _ _ DXT10 de cabeçalho DDS** adicional estará presente para acomodar matrizes de textura ou formatos dxgi que não podem ser expressos como um formato de pixel RGB, como formatos de ponto flutuante, formatos sRGB, etc. Quando a estrutura _ _ DXT10 do cabeçalho do DDS estiver presente, a descrição inteira dos dados será parecida com esta.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;
    
  • Um ponteiro para uma matriz de bytes que contém os dados da superfície principal.A pointer to an array of bytes that contains the main surface data.

    BYTE bdata[]
    
  • Um ponteiro para uma matriz de bytes que contém as demais superfícies; por exemplo, níveis de minimapa, faces de um mapa de cubos, profundidades em uma textura de 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. Siga estes links para saber mais sobre o layout de arquivo DDS para: uma textura, um mapa de cubos ou uma textura de volume.Follow these links for more information about the DDS file layout for a: texture, a cube map, or a volume texture.

    BYTE bdata2[]
    

Muitas ferramentas exportam o formato DDS.Many tools export to the DDS format. Se não houver uma ferramenta para exportar sua textura para esse formato, considere criá-la.If you don't have a tool to export your texture to this format, consider creating one. Para saber mais sobre o formato DDS e como trabalhar com ele no seu código, leia Guia de Programação para DDS.For more detail on the DDS format and how to work with it in your code, read Programming Guide for DDS. No nosso exemplo, usaremos DDS.In our example, we'll use DDS.

Assim como em outros tipos de recursos, você lê os dados em um arquivo como um fluxo de bytes.As with other resource types, you read the data from a file as a stream of bytes. Após a conclusão da tarefa de carregamento, a chamada de lambda executa o código (o método CreateTexture) para processar o fluxo de bytes em um formato que o Direct3D possa 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
            );
    });
}

No trecho anterior, o lambda faz verificações para ver se o nome do arquivo tem uma extensão "dds".In the previous snippet, the lambda checks to see if the filename has an extension of "dds". Se tiver, isso significará que é uma textura DDS.If it does, you assume that it is a DDS texture. Se não, use as APIs WIC (Windows Imaging Component) para descobrir o formato e decodificar os dados como um bitmap.If not, well, use the Windows Imaging Component (WIC) APIs to discover the format and decode the data as a bitmap. De qualquer forma, o resultado será um bitmap Texture2D (ou um erro).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();
    }
}

Após a conclusão do código, a Texture2D estará na memória, carregada de um arquivo de imagem.When this code completes, you have a Texture2D in memory, loaded from an image file. Assim como nas malhas, provavelmente haverá muitas delas no jogo e em determinados cenários.As with meshes, you probably have a lot of them in your game and in any given scene. Considere a criação de caches para texturas acessadas com regularidade, por cenário ou por nível, em vez de carregá-las todas quando o jogo ou o nível é iniciado.Consider creating caches for regularly accessed textures per-scene or per-level, rather than loading them all when the game or level starts.

(O método CreateDDSTextureFromMemory chamado no exemplo acima pode ser explorado totalmente em Concluir código para DDSTextureLoader.)(The CreateDDSTextureFromMemory method called in the above sample can be explored in full in Complete code for DDSTextureLoader.)

Além disso, cada textura ou as "aparências" de textura podem ser mapeadas para polígonos de malha ou superfícies específicas.Also, individual textures or texture "skins" may map to specific mesh polygons or surfaces. Esses dados de mapeamento geralmente são exportados pela ferramenta, artista ou designer utilizado para criar o modelo e as texturas.This mapping data is usually exported by the tool an artist or designer used to create the model and the textures. Lembre-se de capturar também essas informações ao carregar os dados exportados, pois você as usará para mapear as texturas corretas para as superfícies correspondentes, quando executar o sombreamento 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.

Carregando sombreadoresLoading shaders

Sombreadores são arquivos HLSL (High Level Shader Language), que são carregados na memória e invocados em estágios específicos do pipeline gráfico.Shaders are compiled High Level Shader Language (HLSL) files that are loaded into memory and invoked at specific stages of the graphics pipeline. Os sombreadores mais comuns e essenciais são os sombreadores de vértices e pixels, os quais processam cada vértice da malha e os pixels do(s) visor(es) do cenário, 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. O código HLSL é executado para transformar a geometria, aplicar efeitos de luz e texturas e para executar pós-processamento no cenário renderizado.The HLSL code is executed to transform the geometry, apply lighting effects and textures, and perform post-processing on the rendered scene.

Um jogo em Direct3D pode ter vários sombreadores diferentes, cada um deles compilado em um arquivo CSO (Compiled Shader Object, .cso) separado.A Direct3D game can have a number of different shaders, each one compiled into a separate CSO (Compiled Shader Object, .cso) file. Normalmente, não há tantos sombreadores que se torne necessário carregá-los dinamicamente e, na maioria das vezes, basta carregá-los quando o jogo for iniciado ou dependendo do nível (por exemplo, um sombreador para efeitos de chuva).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).

O código da classe BasicLoader fornece algumas sobrecargas para diferentes sombreadores, incluindo sombreadores de vértice, geometria, pixel e envoltório.The code in the BasicLoader class provides a number of overloads for different shaders, including vertex, geometry, pixel, and hull shaders. O código a seguir abrange sombreadores de pixel como exemplo.The code below covers pixel shaders as an example. (Você pode revisar o código completo em Concluir código 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);
    });
}

Neste exemplo, você usa a instância BasicReaderWriter (m _ BasicReaderWriter) para ler o arquivo de objeto do sombreador compilado fornecido (. CSO) como um fluxo 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. Após a conclusão da tarefa, o lambda chama ID3D11Device::CreatePixelShader com os dados de bytes carregados do arquivo.Once that task completes, the lambda calls ID3D11Device::CreatePixelShader with the byte data loaded from the file. O retorno de chamada deve definir algum sinalizador indicando que houve êxito no carregamento e o seu código deve verificar esse sinalizador antes de executar o sombreador.Your callback must set some flag indicating that the load was successful, and your code must check this flag before running the shader.

Sombreadores de vértice são um pouco mais complexos.Vertex shaders are bit more complex. Para um sombreador de vértice, você também carrega um layout de entrada separado, que define os dados de vértice.For a vertex shader, you also load a separate input layout that defines the vertex data. O código a seguir pode ser usado para carregar de modo assíncrono um sombreador de vértice juntamente com um layout de entrada de vértice personalizado.The following code can be used to asynchronously load a vertex shader along with a custom vertex input layout. Verifique se as informações de vértice carregadas das malhas podem ser representadas corretamente por esse layout de entrada.Be sure that the vertex information that you load from your meshes can be correctly represented by this input layout!

Vamos criar o layout de entrada antes de você carregar o sombreador de vértice.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);
    }
}

Nesse layout em particular, os seguintes dados de cada vértice são processados pelo sombreador de vértice:In this particular layout, each vertex has the following data processed by the vertex shader:

  • Uma posição de coordenada 3D (x, y, z) no espaço de coordenada do modelo, representada como um trio de valores de ponto flutuante 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.
  • Um vetor normal do vértice, também representado como três valores de ponto flutuante de 32 bits.A normal vector for the vertex, also represented as three 32-bit floating point values.
  • Um valor de coordenada de textura 2D (u, v), representado como um par de valores flutuantes de 32 bits.A transformed 2D texture coordinate value (u, v) , represented as a pair of 32-bit floating values.

Esses elementos de entrada por vértice são chamados de semântica HLSL e compõem um conjunto de registradores definidos, usados para passar dados para e do 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. O pipeline executa o sombreador de vértice uma vez para cada vértice da malha carregada.Your pipeline runs the vertex shader once for every vertex in the mesh that you've loaded. A semântica define a entrada no (e a saída do) sombreador de vértice à medida que é executada e fornece esses dados para os cálculos por vértice do código HLSL do seu 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.

Agora, carregue o objeto de sombreador de vértice.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);   
        }
    });
}

Nesse código, depois de ter lido os dados de bytes do arquivo CSO do sombreador de vértice, você cria o sombreador de vértice chamando 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. Depois disso, cria o layout de entrada do sombreador no mesmo lambda.After that, you create your input layout for the shader in the same lambda.

Outros tipos de sombreador, como sombreadores de envoltório e geometria, também podem exigir configuração específica.Other shader types, such as hull and geometry shaders, can also require specific configuration. O código completo para vários métodos de carregamento de sombreador é fornecido em Concluir código para BasicLoader e no exemplo de carregamento de recursos do Direct3D.Complete code for a variety of shader loading methods is provided in Complete code for BasicLoader and in the Direct3D resource loading sample.

ComentáriosRemarks

Nesse ponto, você deverá estar apto a entender e criar ou modificar métodos de carregamento assíncrono de recursos e ativos comuns de jogos, como malhas, texturas e 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.