Práticas recomendadas de gerenciamento de recursos
As texturas gerenciadas, também conhecidas como gerenciamento automático de textura, estão disponíveis no DirectX desde a versão 6, com várias revisões e aprimoramentos feitos em versões subsequentes. A partir do lançamento da API do Direct3D 9, o gerenciamento automático de recursos inclui suporte para texturas, buffers de vértice e buffers de índice, tudo com uma interface compartilhada consistente. Usando o gerenciador de recursos do Direct3D, os aplicativos podem simplificar muito o tratamento de situações de dispositivo perdido e podem contar com o sistema para lidar com uma quantidade razoável de excesso de compromisso dos recursos de memória de vídeo.
Às vezes, os desenvolvedores têm dificuldades para usar recursos gerenciados, em parte devido à natureza abstrata do sistema. Embora muitos cenários comuns para recursos sejam adequados para recursos gerenciados, alguns casos têm melhor desempenho ao usar recursos não gerenciados. Este artigo discute as práticas recomendadas para lidar com recursos em geral, como os recursos gerenciados e não gerenciados se comportam e fornece alguns detalhes sobre como os recursos normalmente são tratados pelo runtime e pelos drivers.
Este artigo aborda estes conceitos:
- Memória de vídeo
- Recursos Gerenciados
- Recursos gerenciados pelo driver
- Recursos padrão
- Recursos de memória do sistema
- Recomendações gerais
- Tópicos relacionados
Memória de vídeo
Para que o sistema de vídeo use um recurso, ele deve estar localizado na memória acessível à GPU. A memória de vídeo local fornece o melhor desempenho para a GPU, e determinados recursos (como destinos de renderização e buffers de profundidade/estêncil) devem estar localizados na memória de vídeo local. Com o advento do AGP, a GPU também pode acessar uma parte da memória do sistema diretamente. Essa área de memória, conhecida como abertura do AGP, é conhecida como memória de vídeo não local e não está disponível para outras finalidades. A memória de vídeo não local pode ser lida e gravada na CPU, que normalmente não tem acesso de alto desempenho à memória de vídeo local e, portanto, é ideal para uso como um recurso de memória compartilhada. Uma coisa importante a ser lembrada sobre a memória do AGP é que ela, como a memória de vídeo local, é invalidada em situações de dispositivo perdido e os ativos persistentes localizados lá devem ser restaurados.
Figura 1. Relação da GPU, CPU, RAM de vídeo e RAM do sistema
Algumas soluções de vídeo integradas usam uma UMA (arquitetura de memória unificada), em que main memória é endereçável por todos os componentes dos sistemas. O Direct3D dá suporte à UMA sem exigir nenhuma alteração no aplicativo, utilizando as mesmas dicas que para configurações de memória de vídeo local. Para esses sistemas, os recursos estão sempre localizados na memória do sistema e o driver é responsável por garantir que os recursos funcionem da mesma forma que funcionam em uma arquitetura mais tradicional, aproveitando as propriedades da UMA e qualquer comportamento específico da implementação de hardware.
Figura 2. GPU e CPU têm acesso igual à RAM do sistema em uma arquitetura de memória unificada
Recursos gerenciados
A maioria dos recursos deve ser criada como recursos gerenciados no POOL_MANAGED. Todos os recursos serão criados na memória do sistema e copiados conforme necessário na memória do vídeo. As situações de dispositivo perdido serão tratadas automaticamente a partir da cópia de memória do sistema. Como nem todos os recursos gerenciados são necessários para caber na memória de vídeo de uma só vez, você pode confirmar a memória em excesso em que um conjunto de recursos de trabalho de memória de vídeo menor é tudo o que é necessário para renderizar em qualquer quadro determinado. Observe que é provável que a maior parte dessa memória do sistema de repositório de backup seja paginada para o disco ao longo do tempo, razão pela qual a operação redefinição pode ser lenta devido à necessidade de colocar esses dados de volta para restaurar a memória de vídeo perdida.
O runtime mantém um carimbo de data/hora pela última vez em que um recurso é usado e, quando uma alocação de memória de vídeo falha ao carregar um recurso gerenciado necessário, ele libera recursos com base nesse carimbo de data/hora de forma LRU. O uso de SetPriority tem precedência sobre o carimbo de data/hora, portanto, os recursos mais comumente usados devem ser definidos como um valor de prioridade mais alto. O Direct3D 9.0 tem informações limitadas sobre a memória de vídeo gerenciada pelo driver, portanto, o runtime pode precisar remover vários recursos para criar uma região grande o suficiente para que a alocação seja bem-sucedida. As prioridades adequadas podem ajudar a eliminar situações em que algo é removido e, em seguida, é necessário novamente logo depois. O aplicativo também pode chamar EvictManagedResources para forçar a remoção de todos os recursos gerenciados. Novamente, essa pode ser uma operação demorada para recarregar todos os recursos necessários para o próximo quadro, mas é muito útil para transições de nível em que o conjunto de trabalho muda significativamente e para remover a fragmentação da memória de vídeo.
Uma contagem de quadros também é mantida para permitir que o runtime detecte se o recurso que ele acabou de escolher remover foi usado no início do quadro atual, o que implica uma situação de descomposição em que mais recursos estão em uso em um único quadro do que caberá na memória de vídeo. Isso dispara a política de substituição para alternar para uma forma mru em vez de LRU para o restante do quadro, pois isso tende a ter um desempenho ligeiramente melhor nessas condições. Esse comportamento de thrashing afetará significativamente o desempenho de renderização. Observe que a noção de quadro atual está vinculada ao EndScene, portanto, qualquer aplicativo que faça uso de recursos gerenciados precisa fazer chamadas regulares para esse método.
Os desenvolvedores que procuram encontrar mais informações sobre como os recursos gerenciados estão se comportando em seu aplicativo podem usar a consulta de evento RESOURCEMANAGER por meio da interface IDirect3DQuery9 . Isso só funciona ao usar os runtimes de depuração, portanto, essas informações não podem ser dependentes do aplicativo, mas fornecem detalhes profundos sobre os recursos gerenciados pelo runtime.
Embora entender como o gerenciador de recursos funciona possa ajudar ao ajustar e depurar seus aplicativos, é importante não vincular seu aplicativo com muita força aos detalhes de implementação do runtime ou drivers atuais. As revisões do driver ou as alterações no hardware podem alterar significativamente o comportamento, e as versões futuras do Direct3D terão um gerenciamento de recursos significativamente aprimorado e sofisticado.
recursos do Driver-Managed
Os drivers Direct3D são livres para implementar a funcionalidade de texturas gerenciadas pelo driver, indicada por D3DCAPS2_CANMANAGERESOURCE, o que permite que o driver manipule o gerenciamento de recursos em vez do runtime. Para o driver (raro) que implementa esse recurso, o comportamento exato do gerenciador de recursos do driver pode variar muito e você deve entrar em contato com o fornecedor do driver para obter detalhes sobre como isso funciona para sua implementação. Como alternativa, você pode garantir que o gerenciador de runtime seja sempre usado especificando D3DCREATE_DISABLE_DRIVER_MANAGEMENT ao criar o dispositivo.
Recursos padrão
Embora os recursos gerenciados sejam simples, eficientes e fáceis de usar, há momentos em que usar a memória de vídeo diretamente é preferencial ou até mesmo necessário. Esses recursos são criados na categoria POOL_DEFAULT. Fazer uso desses recursos causa complicações adicionais para seu aplicativo. O código é necessário para lidar com a situação do dispositivo perdido para todos os recursos POOL_DEFAULT e as considerações de desempenho devem ser levadas em conta ao copiar dados para eles. A falha ao especificar USAGE_WRITEONLY ou tornar um destino de renderização bloqueável também pode impor sérias penalidades de desempenho.
Chamar Lock em um recurso de POOL_DEFAULT é mais provável que a GPU seja paralisada do que trabalhar com um recurso de POOL_MANAGED, a menos que use determinados sinalizadores de dica. Dependendo do local do recurso, o ponteiro retornado pode ser para um buffer de memória do sistema temporário ou pode ser um ponteiro diretamente para a memória do AGP. Se for um buffer de memória do sistema temporário, os dados precisarão ser transferidos para a memória de vídeo após a chamada desbloquear . Se o recurso de vídeo não for somente gravação, os dados precisarão ser transferidos para o buffer temporário durante o Bloqueio. Se for uma área de memória AGP, cópias temporárias serão evitadas, mas o comportamento de cache necessário poderá resultar em desempenho lento.
É necessário ter cuidado para gravar uma linha de dados de cache completa em qualquer ponteiro para a memória de abertura do AGP para evitar a penalidade de pente-gravação, que induz um ciclo de leitura-gravação e o acesso sequencial da área de memória é preferencial. Se o aplicativo precisar fazer acesso aleatório aos dados durante a criação e você não quiser usar um recurso gerenciado para o buffer, você deverá trabalhar com uma cópia de memória do sistema. Depois que os dados forem criados, você poderá transmitir o resultado para a memória de recurso bloqueada para evitar pagar uma penalidade alta pela operação de combinação de gravação do cache.
O sinalizador LOCK_NOOVERWRITE pode ser usado para acrescentar dados de maneira eficiente para alguns recursos, mas, idealmente, várias chamadas de Bloqueio e Desbloqueio para o mesmo recurso podem ser evitadas. Fazer uso adequado dos vários sinalizadores de bloqueio é importante para o desempenho ideal, assim como usar um padrão amigável de acesso a dados ao preencher a memória bloqueada.
Usando recursos gerenciados e padrão
A combinação de alocações de recursos gerenciados e POOL_DEFAULT pode causar fragmentação de memória de vídeo e confundir a exibição do runtime da memória de vídeo disponível para recursos gerenciados. Idealmente, você deve criar todos os recursos POOL_DEFAULT antes de usar POOL_MANAGED recursos ou fazer uso da chamada RemoveManagedResources antes de alocar recursos não gerenciados. Lembre-se de que todas as alocações feitas de POOL_DEFAULT que residem na memória de vídeo vinculam a memória para a vida útil do recurso que não está disponível para uso pelo gerenciador de recursos ou para qualquer outra finalidade.
Observe que, ao contrário das versões anteriores do Direct3D, o runtime da versão 9 remove automaticamente alguns recursos gerenciados antes de desistir de uma alocação de recursos não gerenciada com falha por falta de memória de vídeo, mas isso pode potencialmente criar fragmentação adicional e até mesmo forçar um recurso em um local sub-ideal (por exemplo, ter uma textura estática na memória de vídeo não local). Novamente, é melhor alocar todos os recursos não gerenciados necessários antecipadamente e antes de usar todos os recursos gerenciados.
Recursos padrão dinâmicos
Os dados gerados e atualizados em alta frequência não precisam do repositório de backup, pois todas as informações serão recriadas ao restaurar o dispositivo. Normalmente, esses dados são melhor criados em POOL_DEFAULT, especificando a dica USAGE_DYNAMIC, para que o driver possa tomar decisões de otimização ao colocar o recurso, sabendo que ele será atualizado com frequência. Isso normalmente significa colocar o recurso em memória de vídeo não local e, portanto, geralmente é muito mais lento para a GPU acessar do que a memória de vídeo local. Para arquiteturas de UMA, o driver pode escolher um posicionamento específico para recursos dinâmicos para otimizar o acesso de gravação da CPU.
Esse uso é típico para soluções de esfolação de software e sistemas de partículas baseados em CPU preenchendo buffers de vértice/índice, e o sinalizador LOCK_DISCARD garantirá que as paradas não sejam criadas nos casos em que o recurso ainda esteja em uso do quadro anterior. O uso de um recurso gerenciado nesse caso atualizaria um buffer de memória do sistema, que então seria copiado para memória de vídeo e, em seguida, usado para apenas um quadro ou dois antes de ser substituído. Para sistemas com memória de vídeo não local, a cópia extra é eliminada pelo uso adequado desse padrão dinâmico.
As texturas padrão não podem ser bloqueadas e só podem ser atualizadas por meio de UpdateSurface ou UpdateTexture. Alguns sistemas dão suporte a texturas dinâmicas, que podem ser bloqueadas e usam o padrão LOCK_DISCARD, mas um bit de funcionalidades (D3DCAPS2_DYNAMICTEXTURES) deve ser verificado antes de fazer uso desses recursos. Para texturas altamente dinâmicas (vídeo ou procedimento), seu aplicativo pode criar recursos de POOL_DEFAULT e POOL_SYSTEMMEM correspondentes e lidar com atualizações de memória de vídeo usando UpdateTexture. Para atualizações parciais e altamente frequentes, o paradigma UpdateTexture provavelmente é a melhor opção.
Por mais útil que os recursos dinâmicos possam ser, tenha cuidado ao projetar sistemas que dependem muito do envio dinâmico. Os recursos estáticos devem ser colocados em POOL_MANAGED para garantir uma boa utilização da memória de vídeo local e para fazer uso mais eficiente de barramento limitado e main largura de banda de memória. Para recursos semi estáticos, você pode descobrir que o custo de um carregamento ocasional na memória de vídeo local é muito menor do que o tráfego de barramento constante gerado, tornando-os dinâmicos.
Recursos de memória do sistema
Os recursos também podem ser criados em POOL_SYSTEMMEM. Embora não possam ser usados pelo pipeline de gráficos, eles podem ser usados como fontes para atualizar POOL_DEFAULT recursos usando UpdateSurface ou UpdateTexture. Seu comportamento de bloqueio é simples, embora as paralisações possam ocorrer se estiverem em uso por um dos métodos mencionados anteriormente.
Embora residam na memória do sistema, POOL_SYSTEMMEM recursos são limitados aos mesmos formatos e funcionalidades (como o tamanho máximo) compatíveis com o driver de dispositivo. O tipo de recurso POOL_SCRATCH é outra forma de recurso de memória do sistema que pode utilizar todos os formatos e funcionalidades compatíveis com o runtime, mas não pode ser acessado pelo dispositivo. Os recursos de rascunho destinam-se principalmente ao uso por ferramentas de conteúdo.
Figura 3. Recursos de memória na RAM de vídeo, na abertura do AGP e na RAM do sistema
Recomendações gerais
Obter os detalhes técnicos de implementação do gerenciamento de recursos corretos será um longo caminho para atingir suas metas de desempenho para seu aplicativo. Planejar como os recursos são apresentados ao Direct3D e o design arquitetônico em torno de carregar os dados em tempo hábil é uma tarefa mais complicada. Recomendamos várias práticas recomendadas ao tomar essas decisões para seu aplicativo:
- Pré-processar todos os seus recursos. Depender da conversão e otimização de tempo de carga dispendiosas para seus recursos é conveniente durante o desenvolvimento, mas isso coloca uma grande carga de desempenho nos computadores dos usuários. Os recursos pré-processados são mais rápidos de carregar, mais rápidos de usar e oferecem a opção de fazer um trabalho offline sofisticado.
- Evite criar muitos recursos por quadro. As interações de driver necessárias podem serializar a CPU e a GPU, e as operações envolvidas são pesadas, pois geralmente exigem transições de kernel. Espalhe a criação em vários quadros ou reutilize recursos sem criá-los/liberá-los. Idealmente, você deve aguardar vários quadros antes de bloquear ou liberar recursos que foram usados recentemente para renderizar.
- No final do quadro, certifique-se de desassociar todos os canais de recursos (ou seja, fontes de fluxo, estágios de textura e índices atuais). Isso garantirá que as referências pendentes aos recursos sejam removidas antes de fazer com que o gerenciador de recursos mantenha os recursos residentes que, na verdade, não estão mais em uso.
- Para texturas, use formatos compactados (por exemplo, DXTn) com mip-maps e considere fazer uso de um atlas de textura. Eles reduzem consideravelmente os requisitos de largura de banda e podem reduzir o tamanho geral dos recursos, tornando-os mais eficientes.
- Para geometria, use a geometria indexada, pois isso ajuda a compactar recursos de buffer de vértice e o hardware de vídeo moderno é fortemente otimizado em torno da reutilização de vértices. Ao usar sombreadores de vértice programáveis, você pode compactar as informações de vértice e expandi-la durante o processamento do vértice. Novamente, isso ajuda a reduzir os requisitos de largura de banda e torna os recursos de buffer de vértice mais eficientes.
- Evite otimizar demais o gerenciamento de recursos. Revisões futuras de drivers, hardware e sistema operacional podem causar problemas de compatibilidade se o aplicativo estiver ajustado muito fortemente para uma combinação particular. Como a maioria dos aplicativos é associada à CPU, o gerenciamento caro baseado em CPU geralmente causa mais problemas de desempenho do que resolve.
Tópicos relacionados