{"id":31778733,"url":"https://github.com/vsaint1/ogl-metal-renderer","last_synced_at":"2025-10-10T06:57:22.893Z","repository":{"id":316168103,"uuid":"1062170265","full_name":"vsaint1/ogl-metal-renderer","owner":"vsaint1","description":"cross-platform renderer with metal/opengl  example application","archived":false,"fork":false,"pushed_at":"2025-10-06T23:57:30.000Z","size":139,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-07T01:19:56.212Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Objective-C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vsaint1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-22T22:47:24.000Z","updated_at":"2025-10-06T23:57:34.000Z","dependencies_parsed_at":"2025-09-23T05:28:20.997Z","dependency_job_id":"945e8d7c-e245-4fbe-8bd4-fef441b4afcc","html_url":"https://github.com/vsaint1/ogl-metal-renderer","commit_stats":null,"previous_names":["vsaint1/ember","vsaint1/c-particle-system","vsaint1/apple-hello-metal","vsaint1/ogl-metal-renderer"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vsaint1/ogl-metal-renderer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsaint1%2Fogl-metal-renderer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsaint1%2Fogl-metal-renderer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsaint1%2Fogl-metal-renderer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsaint1%2Fogl-metal-renderer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vsaint1","download_url":"https://codeload.github.com/vsaint1/ogl-metal-renderer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsaint1%2Fogl-metal-renderer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002975,"owners_count":26083491,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-10T06:57:20.255Z","updated_at":"2025-10-10T06:57:22.882Z","avatar_url":"https://github.com/vsaint1.png","language":"Objective-C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Do zero a um milhão: Computação Gráfica moderna com diferentes APIs\n\nOlá pessoal! Eu fiz um projeto para brincar com Computação Gráfica (CG), e quero mostrar os primeiros resultados que obtive ao passo que explico alguns ferramentas e conceitos por trás do desenvolvilmento.\n\nO objetivo principal foi sair do primeiro triângulo (podemos dizer que é o equivalente ao \"Hello, World!\" da CG), até a renderização de 1 milhão de cubos, usando principalmente duas APIs gráficas: [OpenGL](https://www.opengl.org/) e [Metal](https://developer.apple.com/metal/). Ao longo o texto vou mostrar como diferentes APIs gráficas impactam performance, controle e complexidade das etapas de execução da aplicação.\n\n---\n\nTLDR:\n\n![Design](docs/design.png)\n\n## Glossário\n\n- Vértice: São pontos no espaço 2D ou 3D que definem a forma de um objeto.\n- Espaço de câmera: É a representação 2D/3D da cena a partir da perspectiva da câmera.\n- CPU: Unidade Central de Processamento, responsável por executar instruções e processar dados.\n- GPU: Unidade de Processamento Gráfico, especializada em renderização de gráficos e processamento paralelo.\n- Driver: Software que permite a comunicação entre o sistema operacional e o hardware físico.\n- Vendor: Fabricante do hardware, como NVIDIA, AMD, Intel, Apple.\n- Draw Call: Uma chamada para a GPU desenhar algo. Muitas chamadas podem ocasionar em `driver overhead`.\n- Driver Overhead: Latência e perda de performance causada pela comunicação excessiva entre CPU \u003c-\u003e GPU.\n- Shader: Pequeno programa que roda na GPU para processar vértices ou pixels.\n- Framebuffer: Área de memória que armazena a imagem renderizada antes de ser exibida.\n- Depth Testing: Técnica que determina a visibilidade dos pixels com base na profundidade.\n\n## Computação Gráfica moderna\n\nAntes de tudo, vamos entender quais são as etapas do processo das quais cosistem a renderização gráfica ou em um termo mais técnico e adequado, a **pipeline gráfica**. São as etapas:\n\n![Pipeline](docs/pipeline.png)\n\n1. **Aplicação (CPU)**: envia vértices e comandos.\n2. **Vertex Shader (GPU)**: transforma vértices em espaço de câmera.\n3. **Rasterização**: converte triângulos em pixels.\n4. **Fragment Shader**: calcula cor e iluminação.\n5. **Framebuffer**: produz a imagem final.\n\nDado esse panorama geral de como funciona a pipeline gráfica, podemos destacar a primeira diferença entre algumas APIs, como OpenGL, abstraem muitos detalhes, enquanto outras, como Metal, Vulkan e DirectX 12 oferecem controle mais direto sobre cada etapa, porém com mais complexidade.\n\n---\n\n## Estrutura da pipeline\n\nA pipeline gráfica moderna é composta por múltiplas etapas programáveis:\n\n- **Input Assembler**: lê vértices e índices do buffer.\n- **Vertex Shader**: transforma posições de modelo para espaço de projeção.\n- **Geometry Shader (opcional)**: gera novos vértices ou primitivas, exemplo prático [LOD](\u003chttps://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)\u003e)\n- **Fragment Shader**: calcula cor, luz e textura de cada pixel.\n- **Depth/Stencil Test**: controla visibilidade e hierarquia de camadas, garantindo que objetos mais próximos da câmera sejam renderizados na frente de objetos mais distantes.\n\n\u003cimg src=\"docs/3D-Pipeline.png\" alt=\"3D Pipeline\" width=\"600\"/\u003e\n\n\u003e Fonte: [Wiki Graphics pipeline](https://en.wikipedia.org/wiki/Graphics_pipeline)\n\n---\n\n## Buffers e recursos\n\nPara que a GPU processe algo, precisamos enviar dados de alguma forma. Isso é feito através de **buffers**.\n\n\n| Tipo                      | Descrição                                                      |\n| ------------------------- | -------------------------------------------------------------- |\n| **Vertex Buffer (VBO)**   | Contém vértices (posição, cor, normal, UV).                    |\n| **Index Buffer (IBO)**    | Define a ordem dos vértices (triângulos).                      |\n| **Uniform Buffer (UBO)**  | Armazena dados uniformes (ex: matrizes, luzes).                |\n| **Storage Buffer (SSBO)** | Dados grandes e dinâmicos (ex: partículas, instâncias).        |\n| **Texture**               | Armazena imagens, normal maps e UV 1D/2D/3D.                   |\n| **UV**                    | Coordenadas de textura, mapeiam uma imagem 2D em um modelo 3D. |\n\n\n\u003e Exemplo de uma estrutura de vértice em C/C++\n\n\n```cpp\n\nstruct Vec2 { float x, y; };\nstruct Vec3 { float x, y, z; };\n\nstruct Vertex {\n    Vec3 position; // Posição 3D\n    Vec3 color;    // Cor RGB\n    Vec2 uv;       // Coordenadas de textura\n};\n\n```\n\n\n\n#### OpenGL (C/C++)\n\n\u003e Exemplo de criação e envio de um Vertex Buffer Object (VBO) em OpenGL\n```cpp\nGLuint vbo;\nglGenBuffers(1, \u0026vbo);\nglBindBuffer(GL_ARRAY_BUFFER, vbo);\nglBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n```\n\n#### Metal (Objective-C++)\n\n\u003e Exemplo de criação e envio de um buffer em Metal\n```objc\nid\u003cMTLBuffer\u003e vbuf = [device newBufferWithBytes:vertices\n                                         length:sizeof(vertices)\n                                        options:MTLResourceStorageModeShared];\n```\n\n---\n\nMostrado um pouco das etapas e como é a assinatura de código de cada API, vamos entender quais são as abordagens computacionais usadas por cada uma para executarem as aplicações.\n\n## OpenGL e sua máquina de estados\n\nA OpenGL segue um modelo declarativo e global.\n\nExemplo: antes de fazer alguma alteração, desenhar algo, ou configurar um estado, é necessário \"ativar\" ou \"vincular\" o objeto ao contexto atual.\n\nExemplo:\n\n\u003e Vincula o VBO atual e todas as operações subsequentes afetarão esse buffer\n```cpp\nglBindBuffer(GL_ARRAY_BUFFER, vbo); \n\n```\n\nNas versões modernas (4.x ou superior), foi introduzido o conceito de Direct State Access (DSA), que consiste em permitir a manipulação de objetos sem vinculá-los ao contexto atual, reduzindo o acoplamento ao estado global.\n\n#### DSA vs Não-DSA (C++)\n\n```cpp\n// 4.5+ (com DSA)\nglCreateVertexArrays(1, \u0026vao);\nglVertexArrayVertexBuffer(vao, 0, vbuf, 0, sizeof(Vertex));\n\n// 3.3 (sem DSA)\nglGenVertexArrays(1, \u0026vao);\nglBindVertexArray(vao);\nglBindBuffer(GL_ARRAY_BUFFER, vbuf);\n```\n\nAs vantagens dessa abordagem são:\n\n- Simples e multiplataforma.\n- Ideal para aprendizado e prototipagem rápida.\n- Ampla documentação e disponibilidade de conteúdos.\n\nAs desavantagens dessa abordagem são:\n\n- Estados globais implícitos.\n- Dificuldade em paralelizar.\n- Dependência de drivers e implementações de vendors.\n- Última atualização significativa em 2017. (Versão 4.6)\n\n---\n\n## Metal e seu controle explícito da GPU\n\nMetal segue um modelo **explícito e segue um fluxo de comandos**. Cada etapa é controlada diretamente, e o desenvolvedor gerencia buffers, estados e sincronização.\n\n```objc\nid\u003cMTLCommandBuffer\u003e cmd = [queue commandBuffer];\nid\u003cMTLRenderCommandEncoder\u003e enc = [cmd renderCommandEncoderWithDescriptor:desc];\n[enc setRenderPipelineState:pipeline];\n[enc setVertexBuffer:vbuf offset:0 atIndex:0];\n[enc drawIndexedPrimitives:MTLPrimitiveTypeTriangle\n                indexCount:index_count\n                 indexType:MTLIndexTypeUInt16\n               indexBuffer:ibuf\n         indexBufferOffset:0];\n[enc endEncoding];\n[cmd presentDrawable:drawable];\n[cmd commit];\n```\n\n**Vantagens**\n\n- Controle total sobre a pipeline de renderização.\n- Performance previsível e eficiente.\n- Total integração com o hardware Apple Silicon.\n\n**Desvantagens**\n\n- Verboso e mais complexo.\n- Exclusivo do ecossistema Apple.\n- Pouco conteúdo disponível.\n- Conteúdos e/ou Tutoriais implementados em diferentes linguagens (Swift, Objective-C/Objective-C++, C++).\n\n---\n\n## Complexidade entre diferentes backends\n\nEmbora a computação gráfica seja bastante complexa, ela revela diferentes níveis de controle dependendo da API usada. Por exemplo, **habilitar o depth testing** (DT) (teste de profundidade) é algo trivial no OpenGL, mas requer um pouco mais de configuração no Metal.\n\nCom a **OpenGL** um simples comando habilita o depth testing, e limpar o buffer de profundidade antes de cada frame é igualmente direto:\n\n```cpp\n// Habilita depth testing\nglEnable(GL_DEPTH_TEST);\n\n// Antes de iniciar um frame para desenhar, apenas será necessário limpar o Depth Buffer\nglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n```\n\nCom a **Metal** o mesmo efeito envolve definir explicitamente um [MTLDepthStencilState](https://developer.apple.com/documentation/metal/mtldepthstencilstate) e associá-lo ao render encoder, mostrando o nível mais baixo e detalhado de acesso ao hardware:\n\n```objc\n// Cria o descriptor do depth state\nMTLDepthStencilDescriptor *depthDesc = [[MTLDepthStencilDescriptor alloc] init];\ndepthDesc.depthCompareFunction = MTLCompareFunctionLess;\ndepthDesc.depthWriteEnabled = YES;\n\n// Cria o depth state e associa ao encoder\nid\u003cMTLDepthStencilState\u003e depthState = [device newDepthStencilStateWithDescriptor:depthDesc];\n[renderEncoder setDepthStencilState:depthState];\n\n```\n\nSem Depth Testing, o resultado visual é incorreto, com objetos mais distantes aparecendo na frente dos mais próximos.\n![Sem Depth Test](docs/no-depth.png)\n\nCom Depth Testing habilitado, a hierarquia visual é respeitada, e os objetos mais próximos ficam na frente dos mais distantes.\n![Com Depth Test](docs/with-depth.png)\n\n## Shaders e Linguagens\n\n**Shaders** são pequenos programas que rodam na GPU, escritos em linguagens específicas para cada API.\n\nAs linguagens de shaders variam entre as APIs:\n\n| API     | Linguagem              | Exemplo (Vertex)                 |\n| ------- | ---------------------- | -------------------------------- |\n| OpenGL  | GLSL                   | `void main()`                    |\n| Metal   | MSL                    | `vertex float4 vertex_main(...)` |\n| Vulkan  | SPIR-V (intermediário) | Compilado via GLSL/HLSL          |\n| DirectX | HLSL                   | `float4 main(...) : SV_Position` |\n\n### Exemplo de Vertex Shader (GLSL)\n\n```glsl\n#version 450 core\nlayout(location = 0) in vec3 a_pos;\n\nuniform mat4 MVP; // Model-View-Projection matrix\n\nvoid main() {\n    gl_Position = MVP * vec4(a_pos, 1.0);\n}\n```\n\n### Exemplo em MSL\n\n```cpp\nvertex float4 vertex_main(const device Vertex* vertices [[buffer(0)]],\n                          uint vid [[vertex_id]],\n                          constant float4x4\u0026 MVP [[buffer(1)]]) {\n    return MVP * float4(vertices[vid].position, 1.0);\n}\n```\n\n---\n\n## Instancing e Batching\n\nRenderizar um milhão de cubos exige técnicas de **batching** e/ou **instancing** — desenhar múltiplas cópias de um mesmo modelo com diferentes transformações.\n\n```cpp\nglDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0, instanceCount);\n```\n\nEm Metal:\n\n```objc\n[enc drawIndexedPrimitives:MTLPrimitiveTypeTriangle\n                indexCount:indexCount\n                 indexType:MTLIndexTypeUInt16\n               indexBuffer:ibuf\n         indexBufferOffset:0\n             instanceCount:instanceCount];\n```\n\nIsso reduz milhões `Draw Calls` para apenas **uma**.\n\n\u003e Imagem Desenhando 1.000.000 de cubos com apenas 1 `Draw Call` em Metal:\n\u003e ![Imagem do resultado final (metal)](docs/metal.png)\n\n\n---\n\n## Técnicas Avançadas\n\n| Técnica                                                              | Descrição                                                                                          |\n| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |\n| **[Frustum Culling](https://en.wikipedia.org/wiki/Viewing_frustum)** | Evita renderizar objetos fora do campo de visão da câmera, economizando recursos de processamento. |\n| **Occlusion Culling**                                                | Evita desenhar objetos ocultos por outros.                                                         |\n| **Depth Pre-Pass**                                                   | Primeira passagem apenas para profundidade.                                                        |\n| **Deferred Shading**                                                 | Armazena informações em buffers intermediários (G-buffer).                                         |\n| **Compute Shaders**                                                  | Usados para cálculos fora da pipeline gráfica tradicional.                                         |\n| **Multi-pass Rendering**                                             | Usado em efeitos como sombras, reflexos e pós-processamento.                                       |\n\n---\n\n## Do Zero a Um Milhão\n\nRenderizando 1.000.000 de cubos com **Batching**, **Depth Stencil**, e **Câmera 3D Livre**.\n\n### Estratégias usadas\n\n- Batching em um único buffer.\n- Depth + stencil para hierarquia visual.\n- Atualização de matrizes no GPU-side.\n\n### Performance\n\n\u003e Hardware usado: MacBook M1 Air 8 GB\n\n| API    | FPS Médio | Quantidade de Cubos | Observações           |\n| ------ | --------- | ------------------- | --------------------- |\n| OpenGL | ~5        | 1.000.000           | Bound por driver      |\n| Metal  | ~55       | 1.000.000           | Uso total do hardware |\n\n---\n\n## Diferenças Principais\n\n| Conceito        | OpenGL                       | Metal                                 |\n| --------------- | ---------------------------- | ------------------------------------- |\n| Modelo          | Estado global                | Controle explícito                    |\n| Command buffers | Implícitos                   | Manuais                               |\n| Shaders         | GLSL                         | MSL                                   |\n| Multiplataforma | Sim                          | Não                                   |\n| Performance     | Driver-bound                 | Próximo ao hardware                   |\n| Paralelismo     | Limitado                     | Nativo                                |\n| Ferramentas     | externas (RenderDoc, Nsight) | Integradas (Xcode GPU Frame Debugger) |\n\n---\n\n## Conclusão\n\nOpenGL continua sendo um excelente ponto de partida, mas APIs como **Metal**, **Vulkan** e **DirectX 12** oferecem controle e performance superiores para aplicações modernas, que é algo crucial para jogos ou simulações em tempo real.\n\n\n## Recursos\n\n### Código Fonte\n\n- [Repositório no GitHub](https://github.com/vsaint1/ogl-metal-renderer)\n- [Minha Game Engine (C++)](https://github.com/vsaint1/ember_engine) - Scriptável em Lua, cross-platform, OpenGL + Metal.\n\n### OpenGL\n- [OpenGL Documentação (Khronos)](https://www.opengl.org/) – Documentação e notícias oficiais.\n- [Learn OpenGL](https://learnopengl.com/) – Tutoriais modernos e exemplos práticos.\n- [Docs.gl](https://docs.gl/) – Referência rápida de funções OpenGL e especificações.\n- [OGLDEV YouTube](https://www.youtube.com/@OGLDEV) – Canal com tutoriais de OpenGL e computação gráfica no geral.\n\n### Metal\n- [Metal Documentação (Apple)](https://developer.apple.com/metal/) – Guia oficial da Apple para Metal.\n- [Metal by Example](https://metalbyexample.com/) – Tutoriais e exemplos práticos de Metal com Swift.\n- [Metal Tutorial](https://metaltutorial.com/) – Tutoriais e exemplos de Metal com a nova API metal-cpp.\n\n### Matemática\n\n- [3Blue1Brown](https://www.youtube.com/@3blue1brown) – Canal do YouTube com vídeos sobre matemática.\n\n\n---\n\n## Créditos\n\n- [Vinicius Gabriel (LinkedIn)](https://www.linkedin.com/in/vsaint1/) – Autor e desenvolvedor.\n- [Lucas Lima (LinkedIn)](https://www.linkedin.com/in/lucaslimanunes/) – Revisão e sugestões de melhorias.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsaint1%2Fogl-metal-renderer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvsaint1%2Fogl-metal-renderer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsaint1%2Fogl-metal-renderer/lists"}