Computação Arte, Computador Canvas


Durante meu tempo no Ensino Médio, não era incomum, especialmente antes das provas finais, fazer um resumo do conteúdo para uma dada matéria. Sempre foi bom como exercício 2 em 1, ajuda a sintetizar e memoriza o conteúdo lido, visto que muitas vezes a leitura passiva de um material pouco cria de aprendizado. Sempre estou querendo conhecer ferramentas e bibliotecas para programação, em diferentes campos, mas uma que tem um lugar especial é a área de jogos, em especial a área gráfica. Pois fica aqui o meu "resumão" de como meu computador virou meu canvas e como, quem sabe, ele também não possa virar o seu. (Siga o conteúdo com o código disponível no GitHub)

O modelo mais fundamental é pensar no computador como quatro elementos, processador, memória e periféricos, assim como o barramento que permite com que estes se comuniquem. O processador é responsável por executar diferentes tipos de instruções, sendo que estas podem ou não envolver outros elementos. A memória é a área de armazenamento, desenhada para que cada posição disponível possa ser unicamente identificada e guarde um volume fixo. O barramento é o meio de comunicação dos diferentes elementos, e periféricos são todos os demais elementos, que inclusive podem realizar funções de processamento (processadores gráficos) e armazenamento (discos). Os quatro elementos possuem em comum três características, vazão, latência e banda, com diferentes manifestações dependendo de qual elemento estamos falando, para o processador, sua vazão é quantas instruções executa por segundo, sua latência é o tempo entre requisitar uma instrução e ela ser atendida e a banda indica quantas instruções são executadas ao mesmo tempo (em paralelo). Esta não é, porém, a forma como em grande parte os desenvolvedores de software lidam com computadores, em muitas situações existe um intermediário que abstrai parte desse modelo e estabelece um protocolo comum para que desenvolvedores interajam com as especificidades de concretizar o modelo abstrato apresentado (ou seja, com os diferentes produtos e fabricantes), este é o papel do Sistema Operacional. O Windows irá remover a minha capacidade de interagir diretamente com o computador, ele oferecerá em contrapartida uma interface pela qual eu posso interagir com ele, e ele sim, realizará toda interação necessária com o computador. Mesmo que muitas vezes essa nuance não seja tão óbvia, pois mesmo que os programas (tipicamente arquivos .exe) que temos rodem suas instruções no processador da máquina, a forma como o Sistema controla quais, como e por quanto tempo cada um desses programas executa é a real abstração que temos que lidar como desenvolvedores. Termos como processos e threads tem significados completamente distintos (quando aplicáveis) para o processador do que para os desenvolvedores, isto se dá pelo fato de que os desenvolvedores estão lidando com a abstração de uma thread, por exemplo, conforme o Windows necessita para seu correto funcionamento, e isso facilitará seu trabalho caso tenha que mapear esse entendimento para o processador em que a instrução terá que ser executada.


Entrando no modelo de abstrações do Sistema Operacional, fica mais fácil abandonarmos o uso de Assembly ou Instruções de Processador para esta comunicação (sorry Prof. Knuth), e passar a utilizar linguagens de programação com maior capacidade de abstração. Visto que tanto o Windows como o Linux usam C\C++, o resto será baseado nestas, quando necessário. Para limitar ainda mais o que vou falar, o Windows é onde me sinto mais confortável em programar graficamente, e Direct3D é um grande nome no meio, sendo interessante ter no "cinto de utilidades". Programação gráfica no Windows é, em minha opinião, a principal forma como desenvolvedores tem seu trabalho percebido pelos usuários, isso marca uma das principais interações que teremos com o sistema por meio de seu sub-sistema gráfico, popularmente conhecido como Win32. Tudo no Windows começa no Processo, essa é a caixa principal de um programa, porém, o Processo em si não executa, curiosamente, para executar instruções no sistema cada processo terá uma ou mais Threads, estas sim ativos executáveis de um programa. Como o Windows permite múltiplos Processos rodando ao mesmo tempo, ele internamente se organizará para conceder a cada thread no sistema um tempo para rodar. O Windows também é responsável por oferecer aos Processos uma interface para a Memória, o que visto que abstrai para cada processo uma total abstração da memória disponível, é denominado Memória Virtual. Isto cobre, em grande parte, dois elementos, a memória e o processador. O barramento, ou mais precisamente, a forma de endereçamento dos elementos na memória, determinam se um sistema é 32 bits ou 64 bits e assim por diante, os números são referência direta ao tamanho do endereço (bit sendo a unidade fundamental, com valor 0 ou 1). O Windows também abstrai isso em grande parte, permitindo virtualizar um sistema 64 bits para aplicações que foram desenhadas para sistemas 32 bits (e que portanto seriam incompatíveis). Os periféricos são o último elemento da abstração original, e é aqui que os diferentes subsistemas do Windows irão atuar, cada periférico tem um conjunto de especificidades que não podem ser generalizadas, um auto-falante não é a mesma coisa que um monitor e não oferece as mesmas formas de uso, necessitando portanto de interfaces distintas. Estas interfaces são oferecidas, em geral, em duas partes, uma é o driver, um software desenhado para atuar mais intimamente com o Windows, tendo acesso mais direto a representação interna das abstrações que mencionei. O driver é responsável por oferecer uma interface no Windows que será utilizada pelos desenvolvedores por meio da segunda parte, o Kit de Desenvolvimento de Software (em inglês, SDK). Esse SDK pode ser oferecido em diferentes linguagens, não sendo restrito a C\C++ (mesmo o driver provavelmente tendo sido desenvolvido nelas), e basicamente contém as instruções necessárias para utilizar a interface oferecida pelo driver. Para o que pretendo expor aqui, iremos interagir com o subsistema gráfico, inicialmente por meio unicamente do Windows, e em um segundo momento, por meio do SDK Direct3D, que oferece abstração do hardware gráfico disponível (e o Windows emula também, caso necessário para aprendizado ou depuração). O subsistema gráfico do Windows também oferece abstração para diferentes formas de Entrada de dados pelo usuário (Input), como o Teclado, Mouse, Telas Sensíveis a Toque e Trackpads


O subsistema gráfico é, bem resumidamente, composto por duas partes, uma que irei grosseiramente chamar de Win32, e outra que é a Interface com o Dispositivo Gráfico (tradução livre de GDI, Graphics Device Interface). Win32 é um modelo de abstração centrado na figura da Janela (Window), e esta como receptáculo de Mensagens. As mensagens podem ser oriundas de qualquer parte do Sistema, e o Windows permite customização destas caso o Processo queira utilizar mensagens para que suas diferentes Janelas se comuniquem (e estas podem ser controladas por diferentes Threads). Não consigo pensar em analogia melhor do que Iceberg para GDI, sua expressão interna ao sistema é enorme, sendo responsável por grande parte da apresentação gráfica que lidamos quando interagimos com o Windows, e principalmente a cola que habilita Win32 e o Gerenciador de Janelas na Área de Trabalho (tradução livre de DWM, Desktop Window Manager, introduzido no Windows Vista e principal responsável pela composição da imagem final da área de trabalho) com a representação interna do Direct3D. GDI também é uma interface de programação (API) para uso direto por desenvolvedores, porém não é tão comum seu uso, visto que com a introdução do DWM, uma nova API passou a ser oferecida, inicialmente chamada de DirectComposition e agora apenas de Composition (ou Composition Layer). Direct3D é por sua vez outro Iceberg, existe uma parte deste que reside internamente no Windows, agindo como um driver, a outra parte é a API que desenvolvedores utilizam para interagir com o hardware gráfico. O Direct3D é quem interage com o driver do hardware gráfico, este muitas vezes não oferece nenhum SDK para desenvolvedores, apoiando-se nos nomes fortes da indústria como Direct3D e OpenGL. Acesso à formas típicas de interagir com o usuário também são oferecidas pelo subsistema gráfico por meio das mensagens, formas específicas como câmera, voz, Kinect entre outras deverão ser customizadas, mas a intuição geral é de que será por meio de uma mensagem destinada a alguma de suas janelas que você receberá o input do usuário.


A forma como C\C++ será apresentado aqui é uma depravação minha, mas assumo responsabilidade. A linguagem consiste em um corpo canônico que se manifesta pela noção de que elementos de memória são organizados em tipos, programas são composições de múltiplas funções, funções operam sobre instâncias de tipos armazenados na memória e pela sintaxe que permite definir estes elementos. As abstrações apresentadas até agora são, em geral, oferecidas aos desenvolvedores por meio destas funções, e portanto são, em parte, dependentes do sistema que as oferece, não sendo portanto parte da linguagem, mas a linguagem, mesmo que implicitamente, tocará alguma dessas abstrações. A estrutura sintática da linguagem é orientada a mapear uma série de operações a serem executadas em sequência, com elementos que permitem manipular essa sequência como condicionais e repetições (loops). Conforme mencionado, caberá a uma Thread executar estas instruções. Estas instruções operam em dados na memória organizados em Tipos, esta abstração porém é parcialmente mapeada para a memória, visto que a memória não terá a compreensão de tipo enquanto o programa executa e cada elemento seu possui um tamanho fixo. A abstração oferecida envolve a estipulação de um tamanho, em bits, para cada elemento de um dado tipo, e a consequente definição do número de elementos na memória necessários para sua representação, assim como um modelo que permita o endereçamento de elementos que leve em conta a abstração de tipos e seus tamanhos. Assim como as informações manipuladas precisam de um armazenamento, as instruções também precisam ser armazenadas e são armazenadas na mesma memória utilizada para suas informações. Isto, se utilizarmos a noção que temos de memória, concede às funções a capacidade de serem endereçadas e utilizadas de forma similar à informação que manipulam. Isto nos leva a uma abstração parcialmente oculta que a linguagem oferece, C\C++ oferecem um modelo de alocação de memória em duas partes, a alocação estática de memória, ou seja, a definição fixa de uma quantidade de memória utilizada por uma função, e a alocação dinâmica de memória, onde porções de tamanho arbitrário serão requisitadas e liberadas ao longo da execução do programa. Note que a linguagem não oferece explicitamente meios para alocação da memória estática, ela é sempre implícita baseado nas variáveis definidas dentro do escopo de uma função, escopo este delimitado por um par de chaves (logo após o cabeçalho que define a função). Variáveis são a forma como a linguagem abstrai o acesso à memória, e são portanto delineadas pelo seu Tipo. A memória necessária para armazenar as variáveis definidas em uma função, assim como parte dos argumentos desta função, definirão o tamanho da memória estática necessário para a função. Dada a forma como os Sistemas Operacionais armazenam o endereço das instruções e a memória estática de uma função, esta porção de memória é denominada Stack (termo inglês para Pilha como em "pilha de pratos"). Repare que a linguagem não diz nada sobre isso, sendo isto muito mais observado se olharmos o programa uma vez concluída sua tradução de C\C++ para as instruções do processador destino. A linguagem também abstrai o conceito de invocar uma função, o que novamente será traduzido para o que o Sistema Operacional deverá executar, e aqui o sistema faz uso da noção de sub-rotina, onde um novo escopo é criado e "empilhado" sobre o anterior, uma vez que esta rotina termina e produz seu resultado, ela é "desempilhada", retornando à rotina anterior e o resultado é oferecido a atual rotina. A memória dinâmica é menos tratada pela linguagem no que se refere a sua alocação, mas recebe especial atenção no endereçamento e acesso. Visto que independentemente de dinâmica ou estática, estamos lidando com a memória, a noção de endereçamento e sua manipulação é comum a ambas, porém uma vez que a restrição de escopo da memória estática não existe na alocação dinâmica, esta é mais comum de utilizar das abstrações da linguagem para sua manipulação. O processo de tradução, chamado de Compilação, envolve mapear as abstrações oferecidas pela linguagem para as abstrações oferecidas pelo Sistema Operacional e concretizar estas em um conjunto de instruções que o processador alvo seja capaz de executar. Uma vez compiladas as unidades funcionais, estas são combinadas e irão formar o programa desejado. Nem todas as unidades funcionais utilizadas por um programa são combinadas em uma única parte (o arquivo executável, no Windows, o .exe). É possível carregar na memória novas unidades funcionais durante a execução de um programa, assim que estas partes se fazem necessárias. Estas funcionalidades adicionais são agrupadas no que é denominado Biblioteca, e visto a natureza dinâmica com que são acessadas recebem, no Windows, o nome de Bibliotecas de Vínculo Dinâmico (Dynamic Link Libraries, DLLs).


Em minha humilde análise, o principal ganho expressivo do C\C++ está na abstração oferecida pelos Tipos de Dados. Digo isto visto que as Instruções disponíveis são similares aquelas que o processador oferece, sendo basicamente Instruções Aritméticas, Lógicas e de Controle da Sequência de Execução (repare que as mesmas estão disponíveis em Assembly, a "linguagem" que utiliza as abstrações originais). Conforme mencionado, a Memória é abstraída na linguagem, ignora-se parcialmente a sua natureza rígida com elementos de tamanho fixo e adota-se no lugar um modelo de Tipos. Os tipos mais fundamentais, conhecidos como Primitivos são utilizados para construir tipos mais complexos, envolvendo agregados compostos desses tipos primitivos e outros agregados. Cada linguagem difere em quais tipos primitivos estão disponíveis e quais são as formas de os compor, e é aqui que C e C++ apresentam a maior distinção entre si, onde C é menos flexível nas formas de representação e composição desses tipos. Os tipos primitivos disponíveis em C++ são números e letras, além de um tipo chamado Bool que possui dois valores possíveis Verdadeiro ou Falso, o tipo Função que identifica as unidades funcionais e um tipo chamado Ponteiro que representa a localização de um elemento de um dado Tipo na memória. Números possuem duas representações tipicamente, uma usada para números inteiros e outra para números que possuam partes fracionárias, estes dois são oferecidos em diferentes níveis de precisão (para fracionários) ou capacidade (para inteiros). Letras, ou como são denominados na área, Caracteres, são muito mais complicados do que aparentam, visto que não são apenas os caracteres derivados do Latim, mas também todo e qualquer outro desejado, o que torna o problema inclusive infinito em natureza. O Windows adota internamente o formato UTF-16 para representação de caracteres, mas isso não é imposto às aplicações que podem utilizar o formato ASCII ou UTF-8, porém é importante considerar qual o formato nativo do sistema e evitar desenhar sua aplicação imaginando um menor consumo de memória quando isto não se concretizará visto que o sistema internamente estará representando os caracteres de outra forma.  A forma mais simples de agregar elementos é denominada Vetor (Array), e consiste em 1 ou mais elementos consecutivamente organizados na memória, como um bloco (e vetores com 1 elemento, apesar de redundante, são inteiramente factíveis). Coloquei o nome em inglês (e passarei a usá-lo), pois visto que o assunto é Computação Gráfica, usar o termo Vetor pode ser bem confuso, visto que isto nada tem em ver com o conceito matemático de Vetor, sendo apenas o nome comum que este arranjo de memória possui. Arrays de caracteres são usualmente chamados de Strings, mas a linguagem não diferencia ou dá tratamento especial para estes. A segunda forma de agregar é por meio de Classes, arranjos não necessariamente homogêneos da memória, e cuja semântica também difere dos arrays. O primeiro ponto diz respeito sobre a possibilidade de associar tipos distintos em uma Classe, por exemplo, podemos criar uma classe com um número inteiro e um fracionário, onde um array sempre possui elementos do mesmo tipo. A distinção semântica é mais notável em C++ (em C não se usa o nome Classe) onde a adição de elementos do tipo Função é feito de maneira implícita nas Classes, assim como é oferecido um novo conjunto de abstrações para composição destas Classes o que permite por exemplo que uma Classe (a Subclasse) possua características de outra Classe (a Superclasse) além de um novo conjunto de características exclusivas. Ponteiros são a parte final da abstração e estes, visto que representam a localização de um elemento que possui um tipo, também possuem um Tipo, são utilizados quando estamos fazendo referência a um elemento na memória. 


Com esta compreensão temos que Programas de Computador são uma coleção de Instruções organizadas em Funções e armazenadas na Memória que operam informação representada na forma de Tipos também armazenados na Memória assim como requisitam interação com demais periféricos através de Funções expostas por meio do Sistema Operacional sejam estas diretamente operadas por este ou oriundas de outros Programas executando nele. O Windows define que uma das Funções será utilizada como ponto de partida do programa, e quais argumentos ele oferecerá a ela quando for invocá-la em resposta ao pedido (do usuário ou de outro programa) de lançamento do programa. Esta função, usualmente denominada Main (Principal em inglês), no Windows recebe o nome WinMain e seus argumentos serão um identificador do programa sendo lançado (um número inteiro), o identificador do programa que lançou este (opcional, sendo oferecido quando aplicável), um ponteiro para uma String que representa a linha de comando passada pelo usuário (contém no mínimo o nome do programa) e um número inteiro que atua como Enumeração (ou seja, cada valor tem um significado associado, como 1 - Vermelho, 2 - Verde e assim por diante) e aqui enumera como o usuário quer que as janelas sejam exibidas (por exemplo, ele pode requisitar que sejam abertas de forma Minimizada). O Windows não espera que esta Função retorne nenhum valor especial, porém informa que esta deve retornar um valor inteiro uma vez encerrada (o significado deste é inteiramente delegado ao desenvolvedor, porém adota-se 0 como significando sucesso ou "nada a reportar"). Tipicamente, programas desejam apresentar uma interface gráfica para interagir com o usuário e encerrar quando este decide (usualmente ao fechar a janela principal do programa ou conjunto de janelas), e isto delineia a estrutura típica de um programa no Windows, ele iniciará criando uma Janela Principal e quaisquer outro recursos que necessite, e repetidamente irá verificar se existem novas mensagens e processar estas caso estejam disponíveis, encerrando as repetições uma vez que receba uma mensagem requisitando término, o programa então libera quaisquer recursos que tenha adquirido e retorna o valor final ao sistema. O Windows oferece duas formas de verificar se existem mensagens, uma que mantém o Programa esperando até que uma mensagem esteja disponível e outra que retorna imediatamente e permite que o Programa execute enquanto nenhuma mensagem lhe é destinada. Para aplicações típicas a primeira forma é mais comum, porém, como estou interessado em produzir uma sequência de imagens a ser apresentada, é preferível utilizar qualquer tempo disponível para a geração destas imagens. 

Direto da caixa, C\C++ oferece um sistema simples, porém robusto, para desenvolvedores referenciarem os tipos e funções criados por outros desenvolvedores e aqueles oferecidos por meio do sistema operacional, são as bibliotecas dinâmicas e estáticas (esta são unidades funcionais combinadas junto com o programa), isto é feito por meio da combinação de Arquivos Cabeçalhos (tradução livre de Header Files) contendo um conjunto de definições de tipos, com as unidades funcionais pré-processadas (ou seja, que já tiveram suas unidades funcionais traduzidas). No Windows, existe um segundo sistema de referenciamento de unidades funcionais e tipos externos ao programa que também será utilizado, o Modelo de Objetos Componentes (tradução livre de COM, Component Object Model), ele oferece flexibilidade ao sistema para exposição das suas interfaces, isto permite a interoperação com outras linguagens de programação preservando as abstrações existentes, além de oferecer de forma dinâmica uma forma limitada de manipulações de tipos. Conforme brevemente mencionado, C\C++ pouco se referem a alocação de memória, sendo implícita a alocação estática e mesmo que C++ ofereça palavras específicas para alocação dinâmica (new e delete), não existem imposições sobre como isso opera, onde e de que forma essa memória está sendo alocada (inclusive sendo passível de alteração pelo desenvolvedor). Isso se deve ao fato de que isso não é uma abstração da linguagem mas sim do Sistema, a linguagem abstrai apenas a noção de tipos, e ainda é o Sistema quem abstrai a alocação da memória para estes tipos. Intuitivamente podemos pensar a alocação dinâmica como sendo o acesso a um grande Array de bits visível por todas as unidades funcionais de um programa, em nada diferenciando em natureza, portanto, da memória estática. O Windows oferece acesso à rotinas de alocação e liberação de memória dinâmica para evitar que desenvolvedores realizem estas tarefas manualmente, e estas funções estão disponíveis de várias formas, inclusive na abstração sugerida na linguagem (estas abstrações que estou colocando como sugeridas compõe o que é conhecido como bibliotecas padrão). Note que programas gráficos, assim como muitos outros, são desenvolvidos para operar por tempo indeterminado, e portanto irão liberar os recursos que não estão utilizando mais. Não existe na linguagem ou nas abstrações iniciais oferecidas pelo sistema, nenhuma menção ao momento em que a liberação dos recursos alocados é feita, isto é uma das tarefas do desenvolvedor e cuidado deve ser tomado para que referências a esta memória não sejam feitas uma vez que ela não está mais disponível (por ter sido liberada ou nunca ter sido alocada), assim como é importante garantir que ao menos uma referência sempre esteja a disposição caso a memória tenha que ser liberada (a falha em cumprir isso implica que informação sobre aquela posição foi perdida e portanto não pode ser liberada caso necessário, o memory leak). Outra característica do COM é incorporar na abstração oferecida o conceito de contagem de referências, o que permite garantir que as duas premissas no uso da memória dinâmica sejam preservadas.


Como estamos falando de Computação, a melhor forma de entendermos Arte será por meio da Geometria, e precisaremos de uma forma analítica desta para representá-la. O elemento mais simples aqui é o ponto, que descreve uma posição absoluta no espaço sem possuir nenhuma dimensão. Da mesma forma que representamos um ponto, podemos imaginar também uma nova entidade, o Vetor, que sai de um ponto origem e termina na localização de outro ponto, possuindo assim uma dimensão, seu comprimento, dado pela distância entre o ponto em questão e o ponto origem. Para definir um sistema é utilizado um conjunto de Vetores cujo comprimento é 1 (chamados Unitários) e a relação entre estes vetores deve ser de total independência, significando que alterações em um não afetam o outro. Como o que vemos é a luz projetada em nossas retinas, imagens são por definição bidimensionais, dado que a retina é uma superfície. Este é a dimensão final desejada, porém, dada a forma dessa retina e demais características do olho, conseguimos concluir que o mundo ao nosso redor não é bidimensional, mas sim tridimensional, e é papel do olho realizar a projeção da informação na superfície bidimensional da retina da melhor forma a captar a compreensão do mundo. Somado a isto, temos a estereoscopia, ou seja, o fato de que o cérebro combina a imagem de dois olhos criando uma única. Este fator também deve ser levado em conta caso a projeção final deva ser desenhada para não incluir a componente estereoscópica (e portanto criar imagens distintas para cada olho). Como o mundo que compreendemos é tridimensional, esta é a dimensionalidade adotada para o sistema, e três vetores perpendiculares entre si (e portanto independentes) são utilizados para definir este sistema (Popularmente chamados de i, j e k criando os respectivos eixos x, y e z). Usando estes elementos é possível definir as diferentes primitivas geométricas necessárias e então compor estas primitivas em uma cena. Com um ponto e um vetor é possível criarmos retas, com um ponto e dois vetores é possível criar planos e como apresentado o espaço é definido pela composição da origem (um ponto) com seus três vetores independentes. Quando em se tratando de estruturas geométricas, pontos são denominados Vértices, sendo uma linha uma estrutura de dois vértices, um triângulo uma com três (contanto que pelo menos um deles não seja colinear com os demais) e assim por diante. Considerando que triângulos são sempre convexos e possuem vértices coplanares é usual adotar esta estrutura como Primitiva para a construção de todas as demais formas geométricas, mesmo que em aplicativos de edição de modelos seja comum o uso de quadriláteros (em aplicativos como Blender ou AutoCAD), internamente eles são representados como dois triângulos. Dada a noção de sistema apresentada, podemos entender o conceito de manipulação da localização de um ponto, ou Transformações. Quando uma mesma transformação é aplicada em todos os vértices de uma primitiva, o efeito é o mesmo de aplicar a transformação na primitiva. Algumas transformações como Rotação e Escala só fazem sentido se pensarmos não em Pontos mas em Primitivas ou Vetores. Para Pontos, toda Transformação se assemelha a uma Translação (mover algo sem deformá-lo ou mudar sua orientação). Um conjunto especial de Transformações, chamado de Transformações Lineares permitem a composição e inversão das Transformações aplicadas, o que por sua vez pode ser usado para uma vez que possuímos um conjunto de Primitivas transformarmos estas para suas respectivas posições e orientações na cena. Transformações podem ser pensadas também como mapeamentos entre diferentes Sistemas, visto que podemos imaginar que estamos aplicando ela nos vetores constituintes do sistema e em seu ponto origem. Transformações Lineares são, simplificadamente, aquelas que mantém a estrutura independente dos vetores do Sistema, e consistem de Rotações (sobre qualquer eixo possível no sistema), Escala (Ampliação ou Redução de tamanho) e Translação. Para que Translações sejam possíveis de ser representadas como Transformações Lineares, é necessário que passemos a utilizar um sistema de 4 coordenadas, esta última, denominada coordenada Homogênea (usualmente w), possui valor 0 quando o elemento é um Vetor e, inicialmente, 1 quando este é um Vértice. A última Transformação fundamental é a de Projeção, conforme vimos existe a necessidade de projetar o mundo tridimensional em uma imagem bidimensional, esta é a Projeção (como uma sombra projetada numa superfície). A forma como a imagem é projetada costuma ser dividida em duas, uma é a perspectiva, onde, similar ao efeito do buraco de agulha (pinhole) em uma câmera, pontos tendem a ser projetados imaginando um ponto focal, o efeito é que objetos distantes parecem menores. A segunda forma é a projeção ortogonal, onde assume-se que o ponto focal é no infinito, e portanto, pontos são projetados sem deformações, neste caso independente da distância, largura e tamanho são preservados. O computador, porém, diferentemente do conceito geométrico ideal, possui um número finito de elementos que compõe a imagem, os pixels. Mapear as primitivas projetadas em uma matriz de pixels é denominado Rasterizar a primitiva, e assim como outros processos de digitalização (quantização e amostragem) sofre de Aliasing, no caso gráfico manifesto na forma de silhuetas serrilhadas além de outros efeitos indiretos devido à interpolação de valores atribuídos aos vértices ao longo dos pixels que compõe a primitva (como a texturização das primitivas). Dentre estes valores interpoláveis, inicialmente o mais intuitivo é a Cor do vértice, cor esta que pode ser definida por um modelo de Iluminação Local, buscando simular a forma como a luz interage e é percebida pelo olho humano. A representação mais comum adotada para cor é o formato RGB (do inglês, Vermelho, Verde e Azul), onde cores são definidas por uma combinação de valores entre 0 e 1 para cada uma das componentes.  Uma vez que todos os pixels visíveis de uma cena foram gerados e combinados na imagem final é possível ainda utilizar esta como entrada para um programa de processamento de imagens ou apresentá-la ao usuário usando um monitor (ou impressora).

Voltando ao computador, será papel do Direct3D abstrair grande parte (a totalidade em muitos casos) do processo de geração de imagem seguindo um modelo de linha de montagem (pipeline) onde a saída de uma etapa produz a entrada da etapa seguinte. GDI e Win32 ainda oferecerão abstrações que devemos interagir até porque não somos o único programa operando no sistema, especialmente no caso do Win32 onde provavelmente toda interface de entrada com o usuário é realizada. Em um esboço do que será o resultado final dessa sessão, quero uma área onde desenharei um objeto tridimensional com projeção perspectiva, e uma área onde colocarei controles tradicionais (como caixas de texto e botões) para regular características do objeto. Para compilação e edição dos arquivos fonte utilizados, farei uso do Visual Studio, que conta com um ambiente integrado de desenvolvimento contando com ferramentas de edição e compilação do programa (versão gratuita é suficiente). Os tipos necessários para representação de primitivas geométricas são todos oferecidos pelo sistema por meio do Direct3D, valores com uma única dimensão são denominados escalares, e a representação de pontos e vetores é a mesma (variando, quando aplicável, na coordenada homogênea) sendo utilizado uma classe contendo 4 escalares e funções diversas para sua manipulação (como obter seu comprimento, a soma de vetores e seu produto interno, este último, basicamente uma medida do ângulo entre eles), assim como cores utilizarão classes contendo 3 escalares (ou 4 considerando um para transparência) um para cada componente. Transformações são representadas como Matrizes, sendo estas arranjos de escalares em um grid bidimensional (linhas e colunas). Esta representação é possível pois podemos multiplicar matrizes entre si, seguindo a simples regra de que cada posição da matriz resultado é a soma do produto dos elementos na mesma linha na primeira matriz pelos elementos na mesma coluna da segunda matriz. Isso também se aplica aos vetores, pois estes podem ser vistos como Matrizes com uma única coluna (ou linha, a depender da convenção desejada). O resultado do produto entre duas matrizes é a combinação das transformações representada por estas matrizes na ordem em que elas foram multiplicadas, reforçando aqui a importância em que a ordem das operações terá no resultado final (e de que produto de matrizes não é comutativo). O resultado, portanto, da multiplicação de uma matriz por um vetor será a transformação deste, gerando assim um novo vetor. Assim como vetores, Direct3D nos oferece abstrações de Matrizes na forma de Classes e suas respectivas operações, assim como formas de obter as matrizes iniciais de transformação (Rotação, Translação, Projeção e assim por diante). Estas informações geométricas são manipuladas pelo Hardware Gráfico seguindo a estrutura de uma linha de montagem (tradução grosseira de pipeline) iniciando-se pela organização e processamento inicial dos parâmetros de entrada (Input Assembler), passando pelo processamento dos vértices onde geralmente são aplicadas as transformações (Vertex Shader) e em seguida por um conjunto de etapas opcionais que flexibilizam as capacidades do pipeline de gerar novas primitivas. As primeiras 3 etapas opcionais são para o processo de Tecelagem de primitivas (Hull Shader, Tesselator, Domain Shader), conjunto de técnicas onde novas primitivas são geradas baseados nos parâmetros de entrada e podem levar em conta inclusive a visibilidade da cena (para granularizar o nível de detalhe de cenas vistas à distância por exemplo), a última etapa antes da Rasterização permite ao desenvolvedor manipulação à nível de primitiva (Geometry Shader), onde etapas anteriores ainda operavam no nível dos vértices (ou pontos de controle no caso da tecelagem). Durante a rasterização também são eliminadas todas as primitivas que estejam fora da área de recorte (Clipping Area), área esta que define qual porção da projeção do mundo estamos interessados em visualizar. É possível também imaginar que primitivas bidimensionais, como triângulos possuem duas faces, visto que podemos coletar os vértices em sentido horário ou anti-horário e estaremos definindo a mesma primitiva. Assim como o recorte de primitivas, durante a rasterização podemos também eliminar faces que estejam "de costas" para a câmera, uma vez escolhida uma convenção para a determinação das faces (sentido horário ou anti-horário para a face frontal). Cabe ressaltar que mesmo projetados, os vértices mantém a informação sobre sua profundidade, informação esta que será interpolada na rasterização e pode ser utilizada posteriormente na hora de combinar pixels que ocupam a mesma posição na imagem. Como descrito, o resultado da rasterização é a conversão de primitivas geométricas em um conjunto de pixels, estes são entregues novamente ao desenvolvedor que poderá manipulá-los (Pixel Shader) antes de serem entregues a última etapa (Output Merger) que consiste em combinar os pixels oriundos do Pipeline com aqueles já presentes na Imagem final. Para simplificar o processo e torná-lo independente da ordem com que primitivas são processadas (na maioria dos casos), utiliza-se de uma cópia da Imagem onde os pixels armazenam a profundidade ao invés da cor (o Z-Buffer, em referência ao eixo que representa profundidade), permitindo que a combinação dos pixels possa utilizar desta informação para determinar qual deles está na frente e qual está atrás. Das etapas discutidas, algumas tem sua funcionalidade fixa porém são configuráveis e outras são completamente desenvolvidas como unidades funcionais para serem executadas pelo processador gráfico, Direct3D utiliza da High Level Shading Language (HLSL) para definição dos Shaders, basicamente programas desenhados para operar nas diferentes etapas customizáveis do Pipeline Gráfico. Cada Shader tem uma semântica distinta, assim como limitações distintas e com distintas expectativas de entradas e saídas, porém todos utilizam da mesma linguagem (com sintaxe similar a do C\C++) e conjunto de abstrações para seu desenvolvimento. Diferentemente do hardware tradicional, o hardware gráfico é feito para possuir um alto paralelismo, e isso impacta diretamente na esturtura dos Shaders. Múltiplas instâncias do mesmo programa irão executar em paralelo, completamente independentes uma das outras (exceções se aplicam no caso do Compute Shader, mas ele não participa diretamente do Pipeline) e atuando numa projeção do total de elementos a serem processados (geralmente um, como um vértice por instância do Vertex Shader). Esse paralelismo também afeta como a informação é difundida para a Memória disponível no Hardware gráfico, a única memória que está disponível para o processador gráfico executar as instruções dos Shaders. Assim como faz-se necessário em C\C++, a compilação dos Shaders de HLSL para o conjunto de instruções do hardware gráfico, também é necessária. Esta compilação ocorre em duas etapas, inicialmente é possível compilar as unidades funcionais para uma linguagem intermediária, visando um processador virtual que abstrai o conjunto de instruções finais que o processador irá possuir. Uma vez que o programa está rodando, será necessário prover ao Direct3D estas instruções na linguagem intermediária para que ele, com a informação concreta do hardware instalado, possa fazer a tradução final e preparar as unidades para execução. Assim como para C\C++, o Visual Studio conta com uma interface para o compilador HLSL que permite a realização do primeiro estágio na mesma ferramenta, assim como ferramentas de edição apropriadas para a linguagem.

Para informar o Visual Studio quais ferramentas utilizar e como utilizar estas ferramentas, o próprio Visual Studio nos oferece um "passo-a-passo", um template que já pré-configura os valores necessários para o compilador assim como cria um conjunto de arquivos que tipicamente compõe o tipo de projeto desejado. Para o meu caso, é possível optar por várias das opções disponíveis por padrão, mas a melhor é "Aplicação para Área de Trabalho do Windows" (Windows Desktop Application). Este template consiste em criar um Projeto para C\C++ com ao menos uma configuração (tipicamente 32bits, mas por vezes 32 e 64 são criadas em conjunto) e contendo nesta configuração a opção de utilizar o SDK do Windows, assim como o conjunto de Funções sugeridas pela linguagem, conhecido como C Runtime. Juntamente com o projeto é criado um arquivo de recursos e seu cabeçalho associado (resources.rc e resources.h), este par de arquivos permite uma edição visual dos elementos que irão compor a interface gráfica. Exceto por menus, não será do interesse desta aplicação utilizar este arquivo. Um arquivo cabeçalho (.h) chamado targetver.h será criado para que a aplicação possa definir qual versão do SDK do Windows deseja utlizar, por padrão ele é preenchido com a inclusão de um cabeçalho (SDKDDKVer.h) que nos oferece a mais recente disponível para o compilador. Um cabeçalho especial é criado, denominado framework.h (ou pch.h, stdafx.h e qualquer outro nome, busque detalhes nas propriedades do arquivo), é criado para definir o conjunto de cabeçalhos comuns a todas as unidades funcionais e que pode ser otimizado para acelerar a compilação (denominado pre-compiled header, visto que é "pré-compilado" para evitar que cada unidade tenha que reprocessar o conjunto comum de cabeçalhos. Por fim, será criado um par arquivo fonte (.cpp) e arquivo cabeçalho (.h) para o conjunto de funções principais do programa. O arquivo fonte vem com a função WinMain parcialmente implementada para criar uma única janela contendo um menu simples com uma opção para fechar o programa e uma para informações sobre o programa (a típica janela About). No topo vemos o conjunto de instruções destinadas ao compilador (chamadas diretivas do pré-processador, são iniciadas com o símbolo #) que informam quais cabeçalhos serão visíveis para as funções definidas neste arquivo, estes cabeçalhos por sua vez vão oferecer Tipos (incluindo Funções) para as unidades que o incluem, assim como possivelmente templates para criar definições de funções diretamente na unidade (como é o caso das estruturas que utilizaremos para armazenamento de número variáveis de elementos). Note que estes cabeçalhos não oferecem definições dos tipos Funções que contém (exceção para os templates), estas por sua vez são oferecidas por unidades funcionais agrupadas em bibliotecas estáticas (.lib) ou dinâmicas (.dll), sendo que o primeiro grupo deve ser informado ao compilador para a geração do programa e o segundo deve ser explicitamente carregado pela aplicação enquanto esta executa. A forma mais imediata de informar o compilador sobre quais bibliotecas são necessárias é por meio das Configurações de Projeto do Visual Studio (nas opções de "Linkagem", em inglês Configuration Properties -> Linker -> Input), alternativamente, podemos informar também por meio da diretiva pragma (seguido da instrução comment(lib, "name.lib")). Inicialmente, no cabeçalho pré-compilado (framework.h) temos a inclusão do cabeçalho windows.h que contém as definições dos Tipos e Funções gerais oferecidos pela plataforma, em particular, pelo subsistema gráfico Win32 (a diretiva define utilizada antes da inclusão serve para regular o volume de Tipos e Funções para remover valores antigos pouco utilizados). Tipicamente, se o nome de um arquivo cabeçalho incluído está cercado por aspas, este arquivo é parte do projeto atual ou de uma biblioteca externa, sendo aqueles cercados por sinal de menor e maior (e.g. <arquivo.h>) sendo cabeçalhos oferecidos pelo Windows ou os cabeçalhos que compõe as bibliotecas padrão do C e do C++ (usualmente, arquivos cabeçalho C++ não possuem uma extensão, como por exemplo <map>), lembrando que isso implicará a utilização do Runtime C, ou seja, de DLLs ou bibliotecas estáticas que implementem as unidades funcionais definidas nos cabeçalhos utilizados, geralmente em C++ os arquivos cabeçalho contém templates que serão portanto definidos diretamente na unidade que os utiliza (sem necessidade de vínculo posterior como DLL ou LIB). Geralmente não existe problema em incluir um arquivo cabeçalho e não utilizar nenhuma de suas definições, porém o mesmo é visto como má-prática podendo inicialmente ampliar o tempo de compilação do projeto, dos cabeçalhos incluídos no arquivo framework.h, inicialmente só windows.h se faz necessário, e dos demais apenas tchar.h será mantido para utilizar tipos e funções que abstraem o tamanho usado pelo caractere (lembrando que o Windows usa UTF-16, de 16 bits ou wide char, e muitos usualmente adotam ASCII que utiliza o char com 8 bits). No arquivo fonte criado para o projeto, no meu caso D3D11Sandbox.cpp, teremos a inclusão dos nossos cabeçalhos, a definição de varíaveis globais (participam de um escopo definido pelo programa, e portanto visiveis para todas as unidades funcionais deste, para unidades definidas em outro arquivo utiliza-se a palavra chave extern na definição desta em um arquivo cabeçalho comum) e de alguns dos tipos funções que definiremos neste arquivo (colocadas aqui ao invés de no arquivo cabeçalho para limitar a visibilidade destes tipos para funções definidas neste arquivo). Conforme apresentado o Windows, por meio do subsistema Win32, nos oferece a abstração da Janela como o elemento visual central de uma aplicação, estas Janelas por sua vez podem ser desenhadas livremente pelo desenvolvedor, mas o Windows oferece uma estrutura hierárquica de Janelas que permite "dividir para conquistar", onde Janelas "filhas" tem responsabilidades específicas, como por exemplo, uma caixa de edição de texto, uma caixa de texto (tipicamente chamado de label) ou botão, a Janela principal por sua vez será composta de um conjunto de Janelas e estas podem também ser constituídas de outras janelas e assim por diante, até que a totalidade da interface esteja definida. Cada uma dessas Janelas é representada por uma Classe de Janela, esta que define um conjunto de características comuns, sendo a peça central, definir a função responsável por processar mensagens destinadas à janela. Estas classes não são parte da abstração da linguagem e portanto devem ser criadas e informadas ao Sistema explicitamente por meio da função RegisterClass (vale frisar que o Windows é uma SDK antiga e atualizou-se ao longo dos anos, muitas funções tem versões mais novas com funcionalidades adicionais, no caso RegisterClassEx). Visto a configurabilidade possível para classes, a função recebe seus parâmetros por meio de um Tipo (outro aspecto comum no Windows, Tipos cuja única finalidade é passar parâmetros para uma Função), o WNDCLASSEX (remova o EX caso esteja lidando com a função original). Para evitar copiar estes tipos durante a invocação da função, esta espera que o desenvolvedor informe um ponteiro para o tipo, ou seja, seu endereço (obtido por meio do operador &), e isto faz com que o Windows precise de uma garantia sobre o tamanho do que está sendo passado, e para tal, o primeiro campo da estrutura é justamente o tamanho desta (cbSize). Estilos de classe são aqueles comuns a todas as janelas da classe, e muitas vezes referem-se a como os recursos desta serão alocados, no nosso caso informamos que movimentos horizontais ou verticais geram necessidade de redesenhar a janela. Aqui já é possível informar recursos comumente associados à janelas, como Menus, Cursores e Ícones, o que se faz por meio de identificadores (Handles) para os objetos (caso estes tenham sido criados). O arquivo recurso utilizado pelo Visual Studio permite armazenar informações sobre estes recursos, e por meio da macro (pequenos códigos substituídos pelo pré-compilador) MAKEINTRESOURCE obtemos o dado necessário para sua construção, o que é passado para a função específica (LoadIcon, LoadCursor). A classe receberá um nome (lpszClassName) e como mencionado, informaremos a Função que processará mensagens destinadas a janelas desta classe, denominada WindowProc, ou WndProc. Importante notar que esta função é comum a todas as janelas de uma dada classe, e seu desenho deve levar isso em consideração, mas é também muito comum haver uma relação um para um entre a Janela e sua Classe, ou seja, apenas uma janela será criada para a classe, tornando a diferença entre os dois termos praticamente irrelevante. Uma vez registrada a classe será criada a janela principal do programa utilizando esta classe, isso é realizado com a função CreateWindow (novamente o sufixo Ex oferece acesso a opções adicionais na criação, ambas porém irão criar o mesmo tipo de objeto) onde juntamente com a classe, informamos o tamanho e posição desejados assim como um conjunto de estilos que queremos aplicar à janela (WS_EX_OVERLAPPEDWINDOW  é um estilo que agrega vários outros e oferece o típico associado a uma janela no Windows, como bordas e botões para minimizar, maximizar e fechar). Nesta função também informamos se a janela pertence a alguma hierarquia (hwndParent) e se a mesma está associada a algum Menu (hMenu, alternativamente utilizado para associar um identificador para a janela caso esta possua o estilo WS_CHILD indicando assim ser uma janela "filha"), por fim informamos a qual programa esta janela pertence (hInstance) e opcionalmente um parâmetro adicional que será enviado a WndProc durante a criação da janela (nullptr nesses casos indicam que o parâmetro não é utilizado). Conforme brevemente discutido, WinMain recebe do sistema um parâmetro (um inteiro usado para Enumeração) que informa como o usuário quer que a janela principal do programa seja aberta (nCmdShow), uma vez que a janela foi criada, podemos utilizar a função ShowWindow para informar o sistema como queremos apresentar a janela, esta função recebe o mesmo tipo enumerador que o passado ao programa, e aqui, a aplicação faz exatamente isto, decidindo apresentar a janela (inicialmente) da forma como o usuário deseja (por padrão este valor é SW_SHOWDEFAULT). Toda vez que a aplicação desejar informar ao sistema que uma de suas janelas teve o conteúdo atualizado e portanto precisa ser redesenhada, esta pode invocar a função UpdateWindow indicando o identificador da janela, o resultado será que o sistema avaliará necessidades pendentes de redesenho gerando possivelmente mensagens para tal (WM_PAINT) e com base nisso atualizará suas representações internas dessa janela. O último recurso que acessaremos do arquivo de recursos são as tabelas de aceleração (Accelerator Tables), basicamente, formas de simplificar na WndProc como mensagens referentes a combinações de teclas (as hotkeys) são informadas, o template do VisualStudio cria uma por padrão que traduz <ALT+?> para uma mensagem WM_COMMAND com o parâmetro IDM_ABOUT. A última etapa do programa é executar repetidamente a validação e processamento das mensagens enviadas às janelas do programa, o que é popularmente denominado Message Loop. A condição de parada é a mensagem WM_QUIT, informando (geralmente a janela principal) que o usuário deseja encerrar a aplicação, para avaliarmos se existe alguma mensagem em uma de nossas filas, utilizamos a função PeekMessage que retorna imediatamente (um valor do tipo Bool) informando a presença ou não de mensagens (alternativamente GetMessage mantém a aplicação em espera até que uma mensagem esteja disponível). Caso haja alguma mensagem na fila, a função pega a primeira destas (podemos considerar uma ordem temporal destas) e copia seu conteúdo na memória endereçada pelo Ponteiro que é passado para a função. Os demais parâmetros da função permitem que filtremos quais mensagens estamos interessados, seja filtrando por janela (hWnd) ou por tipo de mensagens (wMsgFilterMin, wMsgFilterMax) e o último parâmetro informa a função se desejamos remover ou não a mensagem da fila, tipicamente a resposta é sim (PM_REMOVE) para que o programa não entre em uma repetição infinita processando a mesma mensagem. Uma vez que tenhamos uma mensagem em mãos, o primeiro passo é verificar se a mesma não é uma hotkey, isso é feito pela função TranslateAccelerator que retornará um valor diferente de 0 caso tenha efetivamente processado a mensagem, ou seja, caso este valor seja 0, devemos continuar a processar a mensagem em questão, o que é feito por meio da função DispatchMessage, esta função irá invocar a WndProc da janela destino com a mensagem recebida (esta é determinada baseado no identificador de janela que a mensagem possui). Antes de enviar a mensagem é comum realizar a tradução das teclas utilizadas no teclado, processo este feito pela função TranslateMessage, que irá gerar adicionalmente mensagens WM_CHAR caso a mensagem que seja passada seja do tipo WM_KEYDOWN e envolva um dos caracteres ASCII. No caso desta aplicação, uma vez que não existam mensagens a serem processadas, e caso tenhamos inicializado a variável global de nossa Classe que representará a cena a ser exibida, faremos uso de 3 abstrações que esta Classe oferece, a Atualização da Cena (Update), a Geração da Imagem (Render) e a Apresentação desta para o usuário (Present). Por fim, uma vez que a mensagem WM_QUIT foi recebida, a função WinMain simplesmente retorna o valor 0 para o Sistema (tipicamente associado a um término sem problemas, porém não existe semântica fixa para este valor).

A função principal cumpre o papel de delinear o programa, mas não especifica os detalhes necessários para a apresentação da imagem ou mesmo da geração desta. Parte do trabalho de definição destas tarefas está na WndProc associada à janela principal da aplicação, a segunda parte será oferecida por uma classe que denominei D3DWnd. Esta classe utilizará das abstrações de tipo oferecidas pelo C++, no caso a visibilidade dos seus tipos constituintes é controlada por meio das palavras public, protected e private, onde apenas o primeiro permite acesso direto destes tipos por unidades funcionais externas à classe, os demais restringem isto inicialmente as funções desta classe e de todas as classes derivadas desta, e por fim apenas as funções definidas nesta classe. Denomina-se herança o processo de derivar uma classe de outra classe, onde a classe base é a classe "pai" ou "super classe" e a classe derivada é a "filha" ou "subclasse". Esta derivação consiste na classe derivada possuindo todos os tipos que definem a classe base e podendo extender estes com mais tipos ou mesmo substituir as definições dos tipos função (processo denominado sobreescrever a função, override). Conforme mencionado, mesmo que a classe derivada possua os tipos da classe base, ainda assim, é possível que suas unidades funcionais não tenham acesso a estes, visto que os mesmos podem ter sido definidos como private. No caso da classe D3DWnd, não é desejável que classes derivadas executem funções internas de inicialização e liberação de recursos, sendo assim as funções responsáveis por estas atividades (InitializeD3D e ReleaseD3DResources) são definidas como private. Os recursos internos referentes ao Direct3D são inicializados e oferecidos também às classes derivadas (visibilidade protected) para que possam interagir com o Direct3D, estes são geralmente representados por Ponteiros, porém como estes Ponteiros representam recursos do Sistema gerenciados pelo modelo COM, os mesmos utilizam-se de classes desenhadas para sua manipulação (classe template Microsoft::WRL::ComPtr, obtida pelo cabeçalho wrl.h). Para auxiliar classes derivadas na execução de suas atividades é comum oferecer funções auxiliares como protected, visto que estas não são desenhadas para uso direto por funções fora da hierarquia, e aqui é oferecida uma função simples para a leitura de arquivos, processo que será necessário para a carga dos Shaders e posterior envio destes para o hardware gráfico. Dentre os elementos públicos da classe encontram-se duas funções com uma sintaxe ligeiramente distinta por não apresentarem valor de retorno, o Construtor (com o mesmo nome da classe) é invocado toda vez que um objeto é construído (simultâneo com alocação para todos os efeitos, seja implicitamente como no caso estático ou por meio do operador new) e permite inicializar os valores dos tipos constituintes da classe. O Destrutor (mesmo nome da classe precedido pelo símbolo ~) é invocado toda vez que o objeto é liberado (novamente, para todos os efeitos, simultâneo com liberação da memória o que é implícito ao final do escopo no caso estático ou por meio do operador delete). Observo que em casos simples é possível apresentar a definição da função diretamente no arquivo cabeçalho, o mesmo se faz necessário caso a função seja um template. O identificador da janela também é oferecido de forma pública, visto que grande parte da manipulação envolvendo este é feita externamente à classe, assim como dois valores anotados com a palavra static indicando tipos que possuem um valor comum a todas as instâncias desta classe, neste caso, o nome da Classe de Janela a ser utilizado (szWndClass) e a WndProc são anotadas desta forma, a primeira para efetivar a semântica de Classe de Janela do Windows com o entendimento do C++ e a segunda também por necessidade, pois a WndProc é uma função comum a todas as janelas de uma dada classe, e implicitamente, para efetivar a abstração oferecida, C++ irá incorporar na lista de parâmetros de uma função pertencente à classe (e sem a anotação static) um ponteiro para a instância da classe em questão (ponteiro denominado this). A assinatura (lista de parâmetros e ordem destes) da função esperada pelo Windows porém não contempla este parâmetro, recebendo apenas um identificador da janela destino da mensagem, um identificador que enumera os tipos de mensagem, um primeiro parâmetro genérico denominado WParam (geralmente, por razões históricas, não é um ponteiro) e um segundo parâmetro genérico denominado LParam (este ocasionalmente é um ponteiro). Estes parâmetros são genéricos pois seu significado e forma de interpretação dependendem de qual o tipo de mensagem que estamos processando (dado pelo enumerador passado no segundo parâmetro), para acessar seu conteúdo é feita a tradução deste tipo por um processo denominado casting (de maneira simplificada muda a representação e quantidade dos bits na memória buscando preservar ao máximo sua semântica, como passar a representar inteiros como números fracionários sem mudar o valor destes). Por fim temos um conjunto de funções que representam as abstrações oferecidas por esta classe (mencionadas anteriormente, Update, Render e Present) assim como funções para inicialização de recursos dependentes ou não da janela na qual a imagem será apresentada. Algumas destas funções estão acompanhadas da anotação virtual que indica que a função pode ser sobreescrita nas classes derivadas desta (caso seja acompanhado de um "= 0" no final, indica que deve ser sobreescrita e que variáveis deste tipo não podem ser construídas). A intuição neste caso é que a classe D3DWnd realiza toda a interação básica com Direct3D e Win32, oferecendo a suas classes derivadas (e portanto as funções destas) uma abstração mais simplificada enviolvendo a atualização da cena e geração da imagem correspondente, assim como acesso aos recursos inicializados do sistema (aqueles expostos no escopo protected). Como mencionado, a WndProc é a principal forma de interagir com o sistema, uma das primeiras mensagens recebidas por uma janela, WM_CREATE nos informa da finalização da criação interna desta janela, permitindo assim que passamos a poder utilizar seu identificador para funções no sistema que exijam um. No caso particular da Janela Principal do programa, aproveito para criar as duas janelas "filha" que separaram a área visível em uma área utilizada para desenhar (que denominei Canvas) e outra para controles que permitam configurar os objetos da cena (denominei de SideControls), uma vez calculada o tamanho de cada uma destas áreas baseado no tamanho disponível (obtido pela função GetClientRect). No caso do Canvas, será invocada a criação de uma janela da classe definida pela variável static em D3DWnd (registrada logo após o registro da classe da janela principal) e será em resposta a mensagem WM_CREATE na WndProc desta janela que construiremos um objeto de uma classe derivada de D3DWnd contendo a representação da cena, note que a janela é criada com o estilo WS_CHILD e também sem um tamanho definido (isto será definido em resposta à mensagem WM_SIZE que sempre é enviada ao menos uma vez ao final da criação da janela). Para os controles laterais o processo é similar, sendo encapsulado diretamente pela classe ControlManager que possui uma instância única e diferentemente de D3DWnd não oferece recursos por meio da derivação do tipo mas diretamente por meio de suas funções, que permitirão as classes derivadas de D3DWnd a possibilidade de construir o conjunto necessários de controle para manipulação da cena. Ainda na WndProc da Janela Principal do programa, temos parte do código inicial adicionado pelo Template do Visual Studio, a mensagem WM_COMMAND representa (de forma bem simplificada) uma ação interna ao programa, como a escolha de um item no menu, ou pressionar um botão. Detalhes sobre a ação estão disponíveis no WParam passado, sendo que parte dele (acessível por meio da macro HIWORD) contém um identificador atribuído a janela durante sua criação (passado no lugar do identificador de menu) ou o identificador do item de menu. Nos casos de IDM_ABOUT e IDM_EXIT, ambas mensagens se referem as entradas no Menu para abrir uma Janela com informações sobre o programa (About Window) e a outra para encerrar o programa (o que gerará a mensagem WM_QUIT em resposta a destruição da janela principal do programa, conforme descrito na resposta a WM_DESTROY). Caso o identificador recebido não seja nenhum dos dois, entregamos a mensagem para nossa instância do ControlManager para que este avalie se não se trata de uma de suas mensagens. Por fim, a janela principal processa a mensagem WM_SIZE, recebida toda vez que a janela é reposicionada ou redimensionada pelo usuário, neste caso recalcula-se a porção referente ao Canvas e notifica este da mudança usando a função MoveWindow e depois requisita a apresentação desta janela com a função ShowWindow com o valor SW_SHOW.

Conforme mencionado, será em resposta a criação da janela designada para o Canvas que inicializaremos nossa classe representando a cena a ser gerada assim como obter os recursos do sistema que nos abilitam a interagir com o hardware gráfico. A inicialização inicia-se pela criação de um recurso que irá abstrair um hardware (processador e memória) gráfico (podendo haver mais de um disponível) na máquina, denominado Dispositivo (Device). Dentro do conjunto de abstrações oferecidas pelo COM, utilizaremos a noção de fábrica, onde obteremos um tipo com o conhecimento necessário para construir um objeto do tipo que desejamos no sistema e nos retornar uma referência a este (COM abstrai neste caso a residência do objeto na memória e no processo atual ou não). Estas funções retornam valores do tipo HRESULT, identificando o tipo de resultado obtido, e copiam um Ponteiro para o tipo requisitado no endereço passado como parâmetro para a função (o parâmetro será do tipo Ponteiro para um valor do tipo Ponteiro). O Windows oferece o DirectX Graphics Interface (DXGI) para abstrair interações com o dispositivo gráfico, e com a função CreateDXGIFactory2 (disponível em dxgi1_6.h) criaremos uma fábrica capaz de enumerar os adaptadores gráficos (Adapter é o termo que DXGI utiliza para uma placa gráfica instalada no sistema) e dada as capacidades destes escolher um para criarmos o Dispositivo do Direct3D. A enumeração dos adaptadores é feita com a função EnumAdapterByGpuPreference que nos permite estipular como desejamos que múltiplos dispositivos sejam listados (no caso, para listar por ordem de maior desempenho, adicionamos o valor DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE). Uma vez que temos uma referência ao adaptador, podemos passá-la para a função D3D11CreateDevice (disponível em d3d11_2.h) com os parâmetros desejados para o dispositivo e avaliar se foi possível criar o conjunto de abstrações necessárias utilizando este Adaptador. Caso esta operação falhe, podemos escolher entre reduzir os requisitos necessários para o Dispositivo a ser criado (modificando os parâmetros) ou então buscar utilizar outro adpatador disponível no sistema (o Windows oferece um emulador, denominado WARP, que é desenhado para implementar todas as abstrações mesmo sem o suporte completo do hardware). A especificação do conjunto de abstrações desejadas é feita por meio de níveis de funcionalidade (Feature Levels), cada um destes níveis especifica um conjunto de funcionalidades que estarão disponíveis, assim como qual versão limite do modelo apresentado pela linguagem HLSL estará disponível (Shader Model). Dentre os parâmetros de criação informaremos um Array contendo (por ordem de prioridade) os níveis desejados pela aplicação (pFeatureLevels), e em caso de sucesso, a função retornará um dispositivo e também qual o nível de funcionalidade que ele apresenta (pFeatureLevel). Juntamente com o Dispositivo é retornado também uma referência ao Contexto Imediato (Immediate Context), objeto que representa um contexto de execução do Pipeline gráfico e no caso, Imediato implica que comandos de desenho invocados neste contexto realizarão a efetivação imediata dos comandos gerados pelo pipeline no hardware gráfico, em contraste com Contextos Deferidos (Deferred Context) onde as ordens de desenho geram listas de comando para utilização posterior (o que permite o reuso dos comandos por exemplo, ou geração assíncrona por múltiplas threads). Uma vez obtida as referências para o Dispositivo e Contexto Imediato, já podemos informar Classes derivadas que a inicialização de recursos que não dependam das características da Janela destino (como área por exemplo), isso é feito por meio da função CreateDeviceDependentResources (definida como virtual). A segunda etapa da inicialização irá ocorrer em resposta à mensagem WM_SIZE, que irá ajustar os valores referentes à dimensão da área de desenho (largura e altura, width, height) e preparar a porção de memória responsável por armazenar os pixels da imagem que o Dispositivo irá produzir. Denomina-se Buffer estas porções de memória, e para oferecer independência ao desenvolvedor da taxa com que o monitor se atualiza, o Direct3D utiliza o conceito de Cadeia de Trocas (Swap Chain), onde um conjunto de buffers é alocado (tipicamente um par) e enquanto um é apresentado os demais são utilizados para geração das próximas imagens, sendo que a substituição da imagem atual no monitor só ocorre quando este finalizou a última atualização (a troca, ou swap das imagens). Visto que é típico o uso de apenas dois buffers, o buffer utilizado para geração da imagem é denominado back buffer.  Assim como o buffer que armazena a imagem principal, devemos criar também o z-buffer que armazenará as profundidades (é o mesmo buffer utilizado para mascarar porções da imagem, procedimento denominado stencil). Visto que esta função é invocada em resposta ao redimensionamento da janela, talvez já tenhamos uma cadeia criada e portanto o primeiro passo é remover a associação de quaisquer recursos do sistema que façam referência aos buffers presentes na cadeia. Na abstração oferecida pelo Direct3D, a memória não é acessada diretamente pelos estágios do pipeline, mas sim acessada por meio de Visões desta (Views), o que pode inclusive ser utilizado para que o estágio em questão tenha acesso somente a uma porção do buffer. Para inicializarmos ou reinicializarmos os buffers dependentes do tamanho da janela, primeiramente removemos as duas visões típicamente associadas a eles, Render Target View é a visão associada ao back buffer e vinculada ao estágio final do pipeline assim como a Depth Stencil View que representa o z-buffer. Conforme presenciado com a criação da Classe de Janela, a criação de recursos consiste na criação de uma estrutura que define o recurso desejado e suas características, seguido da invocação de uma função para a criação deste informando inicialmente qual o Dispositivo a ser utilizado, e que retornará um valor do tipo HRESULT assim como uma referência ao objeto desejado. Cabe ressaltar que referências a objetos COM devem ser liberadas explicitamente, por isso o uso da classe ComPtr para que isto seja realizado implicitamente no término do escopo (no caso de tipos constituintes da classe, na sua destruição). DXGI oferece mecanismos para que possamos integrar Win32 como Direct3D, isso ocorrerá por meio da criação da Swap Chain que utilizará uma memória comum para o back buffer e para a representação da área visível da Janela. Por fim, utilizamos a cadeia para obter uma referência a este buffer e criamos a visão associada. A função CreateTexture2D é oferecida para criarmos explicitamente buffers bidimensionais, e esta é utilizada para a criação do z-buffer e uma vez criado, cria-se a visão do mesmo. O último recurso inicializado é o Viewport que representa a relação entre o sistema projetado (e portanto 2D) e a área visível disponível, no caso a relação é um para um, onde o tamanho do viewport é o mesmo da área de projeção. A última abstração oferecida pela Classe D3DWnd é a de apresentar a imagem gerada, neste caso, simplesmente é realizada a invocação da função presente oferecida pela Swap Chain, onde o primeiro parâmetro informa que desejamos esperar até o intervalo de sincronização com o monitor.

A classe derivada é denominada BasicScene e é constituída, além dos tipos da classe base, pelos recursos que configuram os diferentes estágios do pipeline assim como os recursos que representam a entrada para este. Durante a inicialização dos recursos independentes da janela na cena, para ilustrar faço uso de abstrações oferecidas para paralelismo (acessível pelo cabeçalho ppltasks.h). Duas funções anônimas (também denominadas funções lambda) são criadas e designadas para executar assíncronamente (o sistema se responsabilizará em criar uma thread para execução), a primeira inicializará os recursos de sistema e Shaders e a segunda inicializará o modelo geométrico (Mesh) que iremos desenhar. A preparação da etapa inicial do pipeline, caracteriza-se pela definição do Tipo que será utilizado como Vértice e se indexaremos estes para construção de primitivas explicíta ou implicitamente. O tipo VertexStructure é definido de forma identica em C++ e em HLSL, permitindo que a representação do vértice seja comum entre o programa e o pipeline gráfico, e para esta cena, a estrutura do vértice contém a posição do mesmo, sua cor e um vetor denominado Normal, vetor este que é um vetor unitário perpendicular ao plano que o vértice pertence (visto que o vértice é um ponto, infinitos planos podem ser adotados, o plano neste caso é o da face frontal da primitiva associada a este vértice). Os três atributos do vértice são do tipo XMLFLOAT3(disponível em DirectXMath.h), tipo este que é constituído por 3 escalares (x,y e z). Como mencionado, algumas etapas do pipeline são completamente customizáveis onde o programa a ser executado é definido usando HLSL, para realizarmos a última etapa de compilação e obtermos uma referência à um programa que poderemos associar posteriormente, utilizamos o Dispositivo e as funções de criação dos respectivos Shaders. Para o Vertex Shader, segunda etapa no pipeline onde vértices são (tipicamente) transformados de seus sistemas locais para o sistema bidimensional resultante da projeção adotada, a função utilizada é CreateVertexShader, onde informamos as instruções pré-compiladas (obtidas por meio do arquivo com extensão .vso, cujo conteúdo foi obtido usando a função auxiliar ReadBytes, que utiliza das rotinas fopen, fread and fclose para acessar o arquivo de forma segura mesmo caso a operação falhe) e obtemos uma referência ao objeto caso bem sucedido. O mesmo processo será aplicado para o Pixel Shader, responsável por definir a cor dos pixels resultantes do processo de rasterização e frequentemente utilizado para aplicação de texturas e iluminação local por pixel ao invés de por vértice. De forma similar, passamos as instruções contidas no arquivo pré-compilado para a função CreatePixelShader e obtemos assim uma referência para o objeto. De forma a configurar o estágio inicial de preparação dos dados (Input Assembler), utilizamos a definição do tipo do vértice esperada pelo Vertex Shader e informamos como o programa irá mapear sua representação para a representação necessária, isto é feito por meio da função CreateInputLayout que receberá um Array de elementos do tipo D3D11_INPUT_ELEMENT_DESC onde cada elemento descreve um dos atributos do vértice, atribuindo a este um nome (este é o mesmo referenciado pelo Shader como semântica ou Semantic Value), um formato (o número, tipo e tamanho dos escalares que o constituem, neste caso usamos RGB com 32 bits para cada posição e valores fracionários), o arranjo destes (qual a ordem dos valores na memória, informado por meio de um offset com relação ao valor anterior) e um valor que indica se os valores são para cada vértice ou para cada instanciação do vértice (este diz respeito ao fato de que um mesmo vértice pode ser reutilizado em múltiplas primitivas, como os vértices nos extremos de um cubo que são referenciados pelas três primitivas que participa). O último recurso independente do tamanho da Janela que inicializamos é um buffer para armazenar parâmetros comuns a todos os vértices da cena (Constant Buffer) como as Transformações a serem aplicadas e parâmetros de Iluminação (como a posição das fontes de luz). Este buffer é criado por meio da função CreateBuffer, sendo importante informar no objeto passado (CD3D11_BUFFER_DESC) que o vínculo desejado para este objeto é D3D11_BIND_CONSTANT_BUFFER. O Modelo que iremos definir (uma esfera) também é um recurso independente da janela, sua construção é feita baseada em um parâmetro da cena que denominei Resolução (Resolution) indicando quantas arestas teremos (ligações entre dois vértices) em cada dimensão, a criação dos vértices é feita em um laço (uma sequência de instruções executadas repetidas vezes) onde para cada uma das faces de um cubo unitário (de lados com comprimento 1) iremos subdividi-la pela resolução em cada dimensão. Para calcular a posição de cada vértice, utilizamos o plano que define a face que estamos subdividindo e calculamos uma posição neste plano baseado na atual iteração, esta posição forma um vetor quando tomamos a origem do sistema, e a normalização deste vetor (processo que transforma um vetor não unitário em unitário preservando sua orientação) nos da a posição correspondente dentro da esfera unitária (uma esfera é definida como a localização geométrica de todos os pontos equidistantes do centro da esfera, no caso o centro é a origem e a distância é 1). Uma cor é atribuída para cada uma das faces, e todos os vértices daquela face compartilharão desta. A normal por sua vez é exatamente a posição do ponto em questão visto que estamos definindo uma esfera unitária (inicialmente se tratarmos o objeto como um cubo, isso não é verdade sendo necessário utilizar o vetor normal ao plano que representa a face que estamos dividindo). Note que operações são feitas com o tipo XMVector, e a tradução destes para os tipos utilizados internamente é feita por meio da função StoreFloat3. Uma vez definida a posição do vértice, utilizamos a atual iteração para informar os índices que compõe a primitiva que iremos desenhar (um triângulo, e seus três vértices requerem três índices), sendo que estes índices são referências a posição do vértice desejado no Array que irá ser entregue ao pipeline. Para evitar acesso à vertices que não existem, a última "linha" e "coluna" da face que estamos processando não é considerada para cálculo dos vértices (os vértices em questão foram referenciados pela "linha" e/ou "coluna" anterior). Utilizo um tipo, denominado Vector (acessível pelo cabeçalho vector), que abstrai um Array quando o tamanho deste não é conhecido de antemão ou quando este varia, este Array Dinâmico é utilizado para armazenar temporariamente os valores calculados, uma vez que todos os valores estão disponíveis (e o número de elementos é conhecido) é necessário criar os recursos necessários para representá-los. Dois buffers serão criados (CreateBuffer), o primeiro, cujo vínculo é D3D11_BIND_VERTEX_BUFFER armazenará os vértices, e portanto devemos informar que seu tamanho total é o produto do tamanho de cada elemento pelo número de elementos, o segundo, cujo vínculo é  D3D11_BIND_INDEX_BUFFER, será utilizado para os índices. Para que durante a criação possamos atribuir o conteúdo, passamos para a função uma referência a um objeto do tipo D3D11_SUBRESOURCE_DATA que contém o endereço para os Arrays temporários utilizados e permitirá assim a cópia destes para os recursos criados. A configuração das Transformações referentes ao posicionamento da cena referente ao observador (ou à câmera, termo geralmente adotado) e a projeção do objeto também são parâmetros constantes para a cena, porém, a projeção da cena leva em conta a relação de aspecto da imagem, isto é, a proporção entre sua largura e sua altura. Esta configuração é feita, portanto, em resposta às modificações no tamanho da janela, e é também dependente desta (razão pela qual situa-se na função CreateWindowSizeDependentResources). Teremos três matrizes de Transformação que mediante o uso do Constant Buffer tornaremos disponível para os estágios do pipeline gráfico. Uma destas matrizes, denominada View, representa a transformação do objeto no sistema de coordenadas comum à cena (conhecido como Sistema Global ou Sistema Mundo, World) para um sistema onde o observador (ou câmera) encontra-se na origem e possui uma orientação para o volume que deseja visualizar (volume este que resultará na área de recorte uma vez projetado, também denominado Frustrum devido ao seu formato quando a projeção é perspectiva). A definição dessa matriz portanto se faz com transformações inversas àquelas que faríamos caso o alvo de nossa transformação fosse o observador. Para simplificar a definição desta matriz, Direct3D oferece a função XMMatrixLookAtLH(LH indica um sistema Left Handed, ou Mão Esquerda, onde a relação entre os três vetores base i, j e k é dada pelo polegar e dedos indicadores e médio da mão esquerda, alternativamente pode-se utilizar RH para um sistema de mão direita) para definirmos uma matriz baseado em três elementos, a posição da câmera, a direção de observação e um vetor que indica qual a posição "para cima" desta câmera (pense que ao apontar uma câmera numa dada direção, podemos girar a câmera ao longo deste eixo, para fixar um dos vários ângulos possíveis é que definimos este vetor). A matriz resultante é construída pensando em vetores como matrizes de uma única linha, porém utilizo a convenção de vetores como uma única coluna e para isso transponho a matriz resultante (transposição envolve tornar as linhas da matriz em colunas da matriz transposta) com a função XMMatrixTranspose e armazeno o valor no constant buffer com XMStoreFloat4x4. A matriz de projeção é calculada levando-se em conta o tipo de projeção desejada e para o caso da projeção perspectiva, o processo consiste em estipular o volume visualizável como uma pirâmide cujo topo encontra-se no ponto focal, e dois planos cortam esta pirâmide, um frontal (Near Plane) e um de fundo (Far Plane). Para delinear o formato deste volume estipulamos também o ângulo que define o campo de visão (Field of View) e a relação de aspecto desejada. Estes parâmetros são informados para a função XMMatrixPerspectiveFovLH (mantendo a convenção da mão esquerda), transpostos e armazenados no Constant buffer. Neste momento, os recursos necessários para a geração da cena estão todos disponíveis, restando agora realizar a sobreescrita das funções de Atualização e Geração da cena. Durante a atualização, calculamos a nova posição do objeto referente ao sistema comum para todos os demais objetos da cena (sistema que denominamos Global ou Mundo), para esta cena temos um objeto girando em torno do eixo y (perpendicular ao plano horizontal da cena) o que terá o ângulo de rotação ajustado em 1 grau a cada quadro gerado. Assim como outras transformações, esta transformação é transposta e armazenada no constant buffer e pode ser obtida por meio da função XMMatrixRotationY (o ângulo deve ser informado em radianos, podendo ser convertido com a função XMConvertToRadians). A etapa final de geração da imagem é onde faremos uso do Contexto Imediato obtido durante a inicialização, inicialmente é importante limpar nossa visão da imagem gerada (Render Target View) que está por sua vez associada ao buffer que o sistema apresentará posteriormente, limpeza  esta que constitui em atribuir uma cor para todos os pixels presentes na visão. O mesmo procedimento é feito na visão associada ao z-buffer (Depth and Stencil View) com o valor inicial de 1 para a profundidade representando assim o plano de fundo (o que implica que todo pixel gerado dentro do volume estará na frente do pixel atualmente no buffer). Visto que o conteúdo desejado para o Constant buffer foi ajustado durante a Atualização da cena, o mesmo precisa ser atualizado, o que é feito por meio da função do contexto UpdateSubresource. Com os recursos preparados e atualizados, iniciamos o processo de associar os objetos construídos com seus respectivos estágios no pipeline e, visto que estamos realizando a definição de primitivas por meio de um Array de índices (e não apenas com uma lista de vértices), invocamos a execução do pipeline por meio da função DrawIndexed. Para ajudar a memorização e uso, as funções utilizadas para associar recursos com estágios do pipeline tem no início um par de letras representando o nome do estágio em questão. No caso, definimos qual visão (ou visões) iremos utilizar para geração da imagem e z-buffer por meio da função OMSetRenderTargets, onde OM neste caso refere-se ao estágio final de combinação dos pixels, Output Merger. Para informarmos as entradas do pipeline utilizamos funções do Input Assembler, IASetVertexBuffers para informar os buffers contendo os vértices e seus atributos, IASetIndexBuffer para informar o buffer contendo os índices que definirão as primitivas a serem rasterizadas, IASetPrimitiveTopology para definir qual primitiva queremos desenhar (uma lista de triângulos, triangle list, constrói um triângulo a cada três índices ou vértices passados, alternativamente, faixas ou strips, criam o primeiro triângulo e um novo a cada vértice ou índice adicionado, usando os dois imediatamente anteriores para esta nova primitiva) e IASetInputLayout para informarmos o objeto que mapeará os vértices passados para o formato esperado pelo Vertex Shader. A configuração do Vertex Shader consiste primariamente em informar o recurso que representa o programa a ser executado para cada vértice sendo processado (por meio da função VSSetShader), programa este que, para esta cena, realiza a transformação do vértice de seu sistema de coordenadas local para o sistema de coordenadas projetado, o que como descrito, consiste em realizar o produto das matrizes de transformação com o vetor representando a posição (ponto, e note que definimos a coordenada homogênea como 1). A definição descrita neste programa do tipo que descreve o resultado (VS_OUTPUT) deve ser a mesma da que é definida no programa utilizado para o Pixel Shader pois será a entrada do mesmo (e deve conter, obrigatoriamente, um tipo associado com a semântica que representa a posição no plano projetado SV_POSITION, sendo estas semânticas similares ao que definimos no Input Layout). Para tal, o Vertex Shader define como saída de seu estágio a posição final do ponto, assim como sua cor, o que no caso é diretamente a cor da face original do vértice (lembrando que construímos a esfera com base em um cubo). Como as matrizes necessárias estão armazenadas no constant buffer, informamos ao estágio qual recurso deverá ser utilizado com a função VSSetConstantBuffers (o que permite associarmos mais de um, útil quando temos parâmetros adicionais como iluminação ou definição de materiais). A última etapa antes da requisição de desenho consiste em informar o programa que atuará como Pixel Shader (PSSetShader), programa este que recebe pixels "candidatos" do rasterizador, por vezes denominados fragmentos (em OpenGL, o estágio similar é denominado fragment shader) e é responsável, principalmente, por definir a cor final deste pixel, o que no caso de uso mais simples proporciona a possibilidade de texturização de primitivas, aplicando uma imagem, a textura, na primitiva. Outra aplicação imediata é proporcionar que a iluminação de uma primitiva seja feita a nível do pixel e não do vértice (e posteriormente interpolada ao longo da primitiva pelo rasterizador), onde neste caso podemos informar as normais transformadas por vértice e receber para cada pixel o valor interpolado desta (normais são utilizadas para iluminação pois indicam a direção perpendicular ao plano que contém o ponto) ou até mesmo utilizarmos de texturas para obter estas normais (processo denominado normal mapping). Para esta simples cena porém, o Pixel Shader apenas atribui o valor cor recebido para o pixel resultante.


Concluo aqui o passeio, faltou apresentar a área designada aos controles laterais, o que não é muito intenso e faz uso da biblioteca Common Controls do Windows, acessível por meio da biblioteca estática comctl32.lib e do cabeçalho CommCtrl.h. Nesse caso, ela segue a noção brevemente esboçada de hierarquia de janelas, e utiliza de um Array Associativo (estrutura onde associamos uma chave a um valor, denominado map em várias linguagens) para associar o identificador da janela com uma rotina preparada para responder à ações relativas a esta janela (no caso, a janela é um controle, como um botão). A classe representando a cena utiliza de funções anônimas para especificar um controle numérico que informará a resolução desejada para esfera, iniciando em 1 e com valor máximo de 100, atualizando em resposta seus recursos internos referentes aos vértices e índices. Diretamente do Visual Studio é possível executar a compilação e execução do programa, o que, tipicamente está configurado para operar em modo de Depuração (Debug Mode), onde é possível observar o programa em execução, pausá-lo e executar passo a passo as instruções, assim como observar quais são as instruções sendo executadas (neste caso, não instruções em C++ mas instruções do processador) assim como observar o conteúdo da memória virtual do processo. Caso o dispositivo tenha sido criado informando a intenção de fazer depuração do pipeline gráfico (mediante inclusão do valor D3D11_CREATE_DEVICE_DEBUG dentre a combinação passada no parâmetro Flags), isso permitirá obter informações de erros mais apuradas, assim como utilizar as ferramentas de captura do Visual Studio que permitem inspeção aprofundada de um quadro (frame) e avaliação estágio a estágio, inclusive com a visualização do resultado do estágio e depuração dos shaders.

E assim, começando com nosso computador como uma simples caixa de quatro elementos, fizemos uma humilde obra de arte, assim como construímos a fundação para muito mais.





Fontes das Imagens (excetuando memes, cujos autores desconheço, e as de autoria própria):
 - Flora Thevoux, copiado de https://www.salon.com/2017/07/29/why-i-work-myself-to-death/


Kudos para o Microsoft Paint 3D, OBS Studio e EZGif.com. Ferramentas bem práticas e intuitivas para geração das imagens utilizadas. 

Kudos também para a Microsoft pelo conjunto de ferramentas disponíveis diretamente da caixa, ou oferecidas gratuitamente que permitem a todos desenvolvedores da plataforma explorar os recursos gráficos disponíveis.  

Comentários