O Walt Disney Animation Studios (WDAS) recentemente fez um presente inestimável para a comunidade de pesquisadores de renderização ao liberar uma
cena completa da ilha do desenho animado Moana . Geometria e texturas para um quadro ocupam mais de 70 GB de espaço em disco. Este é um excelente exemplo do grau de complexidade com que os sistemas de renderização precisam lidar hoje; nunca antes pesquisadores e desenvolvedores envolvidos na renderização fora dos estúdios de cinema puderam trabalhar com cenas tão realistas.
Veja como é o resultado da renderização de uma cena com o pbrt moderno:
Uma ilha de Moana renderizada por pbrt-v3 em uma resolução de 2048x858 com 256 amostras por pixel. O tempo total de renderização em uma instância de 12 núcleos / 24 threads do Google Compute Engine com uma frequência de 2 GHz com a versão mais recente do pbrt-v3 foi de 1 h 44 min 45 s.Por parte da Disney, foi um trabalho enorme, ela teve que extrair a cena do seu próprio formato interno e convertê-lo para o habitual; Agradecimentos especiais a ela pelo tempo gasto em empacotamento e preparação desses dados para uso generalizado. Estou certo de que o trabalho deles será bem recompensado no futuro porque os pesquisadores usam essa cena para estudar os problemas de renderizar cenas com eficiência desse nível de complexidade.
Essa cena já me ensinou muito e me permitiu melhorar o renderizador pbrt, mas antes de entrarmos nisso, contarei uma pequena história para entender o contexto.
O hash que não foi
Muitos anos atrás, enquanto fazia um estágio na equipe de renderização da Pixar, aprendi uma lição curiosa: coisas "interessantes" quase sempre aparecem quando os dados de entrada são transmitidos ao sistema do programa significativamente diferente de tudo o que era antes. Mesmo em sistemas de software bem escritos e maduros, novos tipos de entrada quase sempre levam à descoberta de defeitos desconhecidos em uma implementação existente.
Eu aprendi essa lição pela primeira vez durante a produção de
Toy Story 2 . Um dia, alguém notou que uma quantidade incrível de tempo foi gasta analisando os arquivos de descrição de cenas do RIB. Alguém da equipe de renderização (acho que foi Craig Kolb) lançou o criador de perfil e começou a descobrir.
Verificou-se que a maior parte do tempo de análise era ocupada por pesquisas na tabela de hash usada para
internação de strings . A tabela de hash era bastante pequena, provavelmente com 256 elementos, e quando vários valores foram agrupados em uma célula, ela organizou uma cadeia. Após a primeira implementação da tabela de hash, passou muito tempo e agora havia dezenas de milhares de objetos nas cenas; portanto, uma tabela tão pequena foi rapidamente preenchida e se tornou ineficaz.
Era mais aconselhável simplesmente aumentar o tamanho da tabela - tudo isso acontecia no auge do fluxo de trabalho, para que não houvesse tempo para algum tipo de solução elegante, por exemplo, para expandir o tamanho da tabela ao preenchê-la. Nós fazemos uma alteração em uma linha, reconstruímos o aplicativo, realizamos um teste rápido antes de confirmar e ... nenhuma melhoria de velocidade ocorre. Pesquisando uma tabela de hash leva a mesma quantidade de tempo. Awesome!
Após um estudo mais aprofundado, descobrimos que a função de tabela de hash usada era semelhante à seguinte:
int hash(const char *str) { return str[0]; }
(Perdoe-me, Pixar, se eu revelasse seu código fonte RenderMan ultra-secreto.)
A função "hash" foi implementada na década de 1980. Naquele momento, o programador provavelmente considerou que o custo computacional da verificação do efeito de todos os caracteres na cadeia de caracteres no valor do hash seria muito alto e não valeria a pena. (Eu acho que se houvesse apenas alguns objetos e 256 elementos na tabela de hash da cena, isso seria suficiente.)
Outra implementação obsoleta contribuiu: a partir do momento em que a Pixar começou a criar seus filmes, os nomes dos objetos nas cenas cresceram bastante, por exemplo, “BuzzLightyear / LeftArm / Hand / IndexFinger / Knuckle2”. No entanto, algum estágio inicial do pipeline usou um buffer de tamanho fixo para armazenar os nomes dos objetos e reduziu todos os nomes longos, preservando apenas o final e, com sorte, adicionou reticências no início, deixando claro que parte do nome foi perdida: "... year / LeftArm / Hand / IndexFinger / Knuckle2 ".
Posteriormente, todos os nomes dos objetos que o renderizador viu tinham esse formato, a função hash os reuniu em um único pedaço de memória como “.”. E a tabela hash era na verdade uma grande lista vinculada. Bons velhos tempos. Pelo menos, tendo descoberto, rapidamente corrigimos esse erro.
Inovação intrigante
Esta lição foi lembrada para mim no ano passado, quando Heather Pritchet e Rasmus Tamstorf, da WDAS, entraram em contato comigo e perguntaram se eu estaria interessado em verificar a possível qualidade de renderização da cena de
Moana na
pbrt 1 . Naturalmente, eu concordei. Fiquei feliz em ajudar e queria saber como tudo vai acabar.
O otimista ingênuo dentro de mim esperava que não houvesse grandes surpresas - no final, a primeira versão do pbrt foi lançada há cerca de 15 anos, e muitas pessoas usaram e estudaram seu código por muitos anos. Você pode ter certeza de que não haverá interferência, como a antiga função hash do RenderMan, certo?
Claro, a resposta foi não. (E é por isso que estou escrevendo este e alguns outros posts.) Embora estivesse um pouco decepcionado por o pbrt não ser perfeito "pronto para uso", mas acho que minha experiência com a cena
Moana foi a primeira confirmação do valor de publicar essa cena. ; O pbrt já se tornou um sistema melhor devido ao fato de eu descobrir como lidar com essa cena.
Primeiras renderizações
Após acessar a cena, baixei-a imediatamente (demorou várias horas com minha conexão doméstica à Internet) e a descompactei do tar, recebendo 29 GB de arquivos pbrt e 38 GB de mapas de textura
ptex 2 . Eu tentei alegremente renderizar a cena no meu sistema doméstico (com 16 GB de RAM e uma CPU de 4 núcleos). Depois de voltar ao computador depois de algum tempo, vi que estava congelado, toda a RAM estava cheia e o pbrt ainda estava tentando concluir a análise da descrição da cena. O sistema operacional procurou lidar com a tarefa usando memória virtual, mas parecia impossível. Tendo vencido o processo, tive que esperar mais um minuto antes que o sistema começasse a responder às minhas ações.
A próxima tentativa foi uma instância do Google Compute Engine, que permite usar mais RAM (120 GB) e mais CPU (32 threads em 16 CPUs). A boa notícia foi que o pbrt conseguiu renderizar a cena com sucesso (graças ao trabalho de Heather e Rasmus para convertê-la no formato pbrt). Foi muito emocionante ver que o pbrt pode gerar pixels relativamente bons para conteúdo de filme de alta qualidade, mas a velocidade não foi tão surpreendente: 34 min 58 s apenas para analisar a descrição da cena e durante a renderização do sistema gastou até 70 GB de RAM.
Sim, havia 29 gigabytes de arquivos de descrição de cena no formato pbrt no disco que precisavam ser poupados, então eu não esperava que o primeiro estágio levasse alguns segundos. Mas passa meia hora antes mesmo que os raios comecem a traçar? Isso complica muito o trabalho com a cena.
Por outro lado, essa velocidade nos dizia que algo de muito mau cheiro provavelmente estava acontecendo no código; não apenas “a inversão da matriz pode ser realizada 10% mais rápido”; antes, algo no nível de "oh, estamos passando por uma lista vinculada de 100 mil elementos". Fiquei otimista e esperava que, depois de descobrir isso, pudesse acelerar significativamente o processo.
As estatísticas não ajudam
O primeiro lugar em que comecei a procurar pistas foi as estatísticas do pbrt dump após a renderização. Os principais estágios da execução do pbrt são configurados para que você possa coletar dados aproximados de criação de perfil, corrigindo operações com interrupções periódicas durante o processo de renderização. Infelizmente, as estatísticas não nos ajudaram muito: segundo relatos, quase 35 minutos antes do início da renderização, foram gastos 4 minutos e 22 segundos na construção do BVH, mas nenhum detalhe foi fornecido sobre o restante do tempo.
Construir BVH é a única tarefa computacional significativa executada durante a análise de cenas; tudo o resto é essencialmente uma desserialização da geometria e das descrições dos materiais. Saber quanto tempo foi gasto na criação do BVH deu uma compreensão de quão (in) eficaz era o sistema: o tempo restante, aproximadamente 30 minutos, analisou 29 GB de dados, ou seja, a velocidade era de 16,5 MB / s. Analisadores JSON bem otimizados, executando essencialmente a mesma tarefa, operam a uma velocidade de 50-200 MB / s. Claramente, ainda há espaço para melhorias.
Para entender melhor o que está perdendo tempo, lancei o pbrt com uma ferramenta de
perf Linux que nunca havia usado antes. Mas, ao que parece, ele lidou com a tarefa. Eu o instruí a procurar caracteres DWARF para obter nomes de funções (
--call-graph dwarf
) e, para não obter arquivos de rastreamento de 100 GB, tive que diminuir a taxa de amostragem de 4000 para 100 amostras por segundo (
-F 100
). Mas com todos estes parâmetros foi ótimo e fiquei agradavelmente surpreendido que a ferramenta
perf report
tem uma interface com um bom maldições.
Aqui está o que ele poderia me dizer depois de começar com pbrt:
Eu não estava brincando quando falei sobre a "interface com boas maldições".Vemos que mais da metade do tempo é gasto na mecânica de análise:
yyparse()
é o
yyparse()
gerado pelo
bison e
yylex()
é o analisador lexical (lexer) gerado pelo
flex . Mais da metade do tempo em
yylex()
é gasto em
strtod()
, que converte seqüências de caracteres em valores duplos.
yyparse()
ataque para
yyparse()
e
yylex()
até o terceiro artigo desta série, mas agora já podemos entender que pode ser uma boa ideia reduzir a quantidade de dados lançados no renderizador.
Do texto para PLY
Uma maneira de gastar menos tempo analisando dados de texto é convertê-los em um formato analisado com mais eficiência. A maioria dos 29 GB desses arquivos de descrição de cenas são malhas triangulares e o pbrt já possui suporte nativo
ao formato PLY , que é uma representação binária eficaz de malhas poligonais. Também no pbrt há um sinalizador de linha de comando
--toply
, que analisa o arquivo de descrição da cena pbrt, converte todas as malhas de triângulo encontradas em arquivos PLY e cria um novo arquivo pbrt que se refere a esses arquivos PLY.
O problema é que
as texturas
ptex são ativamente usadas na cena da Disney, que, por sua vez, exige que um valor
faceIndex
seja associado a cada triângulo, que determina de qual face a sub-malha original é retirada. Para transferir esses valores, basta
adicionar o suporte a novos campos no arquivo PLY . Pesquisas posteriores revelaram que, no caso de converter cada malha - mesmo que tenha apenas uma dúzia de triângulos - em um arquivo PLY, dezenas de milhares de pequenos arquivos PLY são criados na pasta e isso cria seus próprios problemas de desempenho; Conseguimos nos livrar desse problema adicionando à implementação a
capacidade de deixar pequenas malhas inalteradas .
Escrevi um
pequeno script de linha de comando para converter todos os arquivos
*_geometry.pbrt
em uma pasta para usar o PLY em malhas grandes. Observe que ele possui suposições codificadas sobre caminhos que precisam ser alterados para que o script funcione em outro lugar.
Aumento da primeira velocidade
Após converter todas as malhas grandes em PLY, o tamanho da descrição da cena no disco diminuiu de 29 para 22 GB: 16,9 GB de arquivos de cena pbrt e 5,1 GB de arquivos binários PLY. Depois de converter o tempo total da primeira fase do sistema foi reduzida para 27 minutos e 35 segundos, e a poupança de 7 minutos e 23 segundos, isto é, que acelerou 1,3 vezes
três. O processamento de um arquivo PLY é muito mais eficiente do que o processamento de um arquivo de texto pbrt: apenas 40 segundos de tempo de inicialização foram gastos na análise de arquivos PLY, e vemos que os arquivos PLY foram processados a uma velocidade de cerca de 130 MB / s, ou cerca de 8 vezes mais rápido que o formato de texto pbrt .
Foi uma boa vitória fácil, mas ainda tínhamos muito o que fazer.
Da próxima vez, descobriremos onde toda a memória é realmente usada, corrigiremos alguns erros aqui e obteremos ainda mais velocidade no processo.
Anotações
- Agora você deve entender melhor a motivação para adicionar suporte a ptex da minha parte e converter o Disney BSDF em pbrt no ano passado.
- Todo o tempo aqui e nas postagens subseqüentes é indicado para a versão WIP (Work In Progress), com a qual trabalhei antes do lançamento oficial. Parece que a versão final é um pouco maior. Vamos nos ater aos resultados que gravei ao trabalhar com a cena original, apesar de eles não corresponderem exatamente aos resultados da versão final. Suspeito que as lições deles possam ser as mesmas.
- Observe que o aumento na velocidade é essencialmente o que você esperaria com uma redução de aproximadamente 50% no volume de dados de análise. A quantidade de tempo que gastamos de acordo com o criador de perfil confirma nossa ideia.