Arquitetura
March 15, 2026 · View on GitHub
Visão Geral
O Materialize CLI segue uma arquitetura em camadas com processamento GPU via compute shaders:
┌────────────────────────────────────────────────────────────────┐
│ CLI (main.rs) │
│ clap para argumentos │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Pipeline (pipeline.rs) │
│ Orquestra: Diffuse → Height → Normal → Metallic │
│ Gerencia dependências entre mapas (Height necessário p/ Normal) │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ GPU Context (gpu.rs) │
│ - Instance/Adapter/Device/Queue (wgpu) │
│ - Texture management (input/output buffers) │
│ - Compute pipeline setup │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ Compute Shaders (WGSL) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ height │ │ normal │ │ metallic │ │
│ │ .wgsl │ │ .wgsl │ │ .wgsl │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ I/O (io.rs) │
│ - image crate: PNG/JPG/TGA/BMP/EXR │
│ - GPU↔CPU texture transfer │
└────────────────────────────────────────────────────────────────┘
Componentes
1. CLI (main.rs + cli.rs)
Responsabilidade: Parse de argumentos e orquestração de alto nível
Tecnologia: clap com derive macros
Fluxo:
- Parse argumentos
- Validar input
- Inicializar GPU
- Executar pipeline
- Salvar outputs
- Reportar resultado
2. Pipeline (pipeline.rs)
Responsabilidade: Coordenar a execução dos shaders na ordem correta
Fluxo de processamento:
Diffuse Input
│
▼
┌─────────────┐
│ Height │ ──► height_texture (R32Float)
│ Shader │
└─────────────┘
│
▼
┌─────────────┐
│ Normal │ ──► normal_texture (RGBA8Unorm)
│ Shader │ (usa height como input)
└─────────────┘
│
▼
┌─────────────┐
│ Metallic │ ──► metallic_texture (R8Unorm)
│ Shader │ (usa diffuse original)
└─────────────┘
Otimização: Shaders executam em sequência sem readback CPU intermediário
3. GPU Context (gpu.rs)
Responsabilidade: Abstrair interação com wgpu
API Pública:
pub struct GpuContext {
device: wgpu::Device,
queue: wgpu::Queue,
}
impl GpuContext {
pub async fn new() -> Result<Self>;
pub fn create_texture(&self, size: Extent3d, format: TextureFormat) -> Texture;
pub fn create_compute_pipeline(&self, shader: &str, entry_point: &str) -> ComputePipeline;
pub fn dispatch(&self, pipeline: &ComputePipeline, bind_group: &BindGroup, workgroups: (u32, u32, u32));
pub fn read_texture(&self, texture: &Texture) -> Vec<u8>;
}
Configuração wgpu:
- Backend: Vulkan (Linux), Metal (macOS), DX12 (Windows)
- Power preference: High performance
- Limits: Default
- Features: Shader float32 filtering (se disponível)
4. Shaders WGSL (src/shaders/)
Responsabilidade: Processamento de imagem em GPU
Estrutura: Cada shader é um arquivo .wgsl independente
Compilação: Shaders são embutidos no binário via include_str!()
Workgroup size: 8x8x1 (64 threads por workgroup, bom equilíbrio para GPUs modernas)
5. I/O (io.rs)
Responsabilidade: Leitura e escrita de imagens
Formatos suportados:
| Formato | Leitura | Escrita | Observação |
|---|---|---|---|
| PNG | ✓ | ✓ | Recomendado (lossless) |
| JPEG | ✓ | ✓ | Lossy, configurável |
| TGA | ✓ | ✓ | Games legacy |
| BMP | ✓ | ✗ | Leitura apenas |
| EXR | ✓ | ✓ | HDR, recomendado para normais |
Tecnologia: image crate
Conversão de formatos:
- RGBA8Unorm (u8) para R32Float (f32) - upload para GPU
- R32Float/RGBA8Unorm para bytes - download da GPU
Fluxo de Dados
Upload (CPU → GPU)
Imagem PNG/JPG (CPU)
│
▼
┌──────────────┐
│ image crate │ ──► DynamicImage
│ (decode) │
└──────────────┘
│
▼
┌──────────────┐
│ to_rgba8() │ ──► Vec<u8> RGBA
└──────────────┘
│
▼
┌──────────────┐
│ wgpu::Queue │ ──► write_texture()
│ (upload) │
└──────────────┘
│
▼
GPU Texture (RGBA8Unorm)
Processamento (GPU)
input_texture (RGBA8Unorm)
│
▼
┌─────────┐
│ Height │ ──► height_texture (R32Float)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│ Normal │ ──► normal_texture (RGBA8Unorm)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│Metallic │ ──► metallic_texture (R8Unorm)
│ Shader │
└─────────┘
Download (GPU → CPU)
output_texture (GPU)
│
▼
┌──────────────┐
│ CommandEncoder │ ──► copy_texture_to_buffer()
│ (encode) │
└──────────────┘
│
▼
┌──────────────┐
│ wgpu::Queue │ ──► submit()
│ (submit) │
└──────────────┘
│
▼
┌──────────────┐
│ buffer.slice() │ ──► get_mapped_range()
│ (map_async) │
└──────────────┘
│
▼
Vec<u8> (CPU)
│
▼
┌──────────────┐
│ image crate │ ──► save()
│ (encode) │
└──────────────┘
│
▼
Arquivo PNG
Estrutura de Diretórios
materialize-cli/
├── Cargo.toml
├── README.md
├── docs/
│ └── (documentação)
├── src/
│ ├── main.rs # Entry point
│ ├── lib.rs # Public API (para usar como crate)
│ ├── cli.rs # CLI argument parsing
│ ├── pipeline.rs # Pipeline orquestração
│ ├── gpu.rs # GPU abstraction
│ ├── io.rs # Image I/O
│ └── shaders/
│ ├── height.wgsl # Height map shader
│ ├── normal.wgsl # Normal map shader
│ └── metallic.wgsl # Metallic map shader
├── tests/
│ ├── integration_tests.rs
│ └── fixtures/ # Imagens de teste
└── examples/
└── batch_convert.rs # Exemplo de uso programático
Dependências
Runtime
| Crate | Versão | Propósito |
|---|---|---|
| wgpu | 0.19 | Compute shaders GPU |
| pollster | 0.3 | Runtime async blocking |
| image | 0.24 | Decode/encode de imagens |
| clap | 4.5 | CLI argument parsing |
| anyhow | 1.0 | Error handling |
Dev
| Crate | Versão | Propósito |
|---|---|---|
| tempfile | 3.10 | Arquivos temporários para testes |
Decisões de Design
Por que wgpu e não OpenGL direto?
- Moderno: API unificada para todas as plataformas
- Compute shaders: Projetado para GPGPU, não só gráficos
- Seguro: Validação em tempo de compilação e runtime
- Futuro: Baseado no padrão WebGPU, futuro-proof
Por que compute shaders e não fragment shaders?
- Direto: Sem precisar de render pipeline, vertex buffers, framebuffers
- Simples: Um shader = uma função de compute
- Eficiente: Sem overhead de rasterização
Por que Rust?
- Performance: Zero-cost abstractions, controle de memória
- Segurança: Ownership evita data races naturais em GPU code
- Ecossistema: wgpu é primariamente Rust
- Deploy: Binário único, sem runtime
Considerações de Performance
Otimizações aplicadas:
- Minimize CPU↔GPU transfers: Apenas upload inicial e download final
- Texture arrays: Reutilize textures intermediárias se possível
- Workgroup size: 8x8 = 64 threads (warpsize comum)
- Formatos eficientes: R32Float para height, R8Unorm para metallic
Possíveis melhorias futuras:
- Tiled processing: Para imagens maiores que GPU memory
- Async pipeline: Paralelizar upload/process/download de múltiplas imagens
- Mipmap chain: Usar mips para blur multi-level mais eficiente