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 → Smoothness │
│ → Edge → AO │
│ 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 │ │smoothness│ │ edge │ │ ao │ │
│ │ .wgsl │ │ .wgsl │ │ .wgsl │ │ .wgsl │ │ .wgsl │ │ .wgsl │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────┬───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ I/O (io.rs) │
│ - image crate: PNG/JPG/TGA/BMP/EXR │
│ - GPU↔CPU texture transfer │
└────────────────────────────────────────────────────────────────┘
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
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)
└─────────────┘
│
▼
┌─────────────┐
│ Smoothness │ ──► smoothness_texture (R8Unorm)
│ Shader │ (usa diffuse + metallic)
└─────────────┘
│
▼
┌─────────────┐
│ Edge │ ──► edge_texture (R8Unorm)
│ Shader │ (usa normal como input)
└─────────────┘
│
▼
┌─────────────┐
│ AO │ ──► ao_texture (R8Unorm)
│ Shader │ (usa height como input)
└─────────────┘
Otimização: Shaders executam em sequência sem readback CPU intermediário
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)
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)
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
Imagem PNG/JPG (CPU)
│
▼
┌──────────────┐
│ image crate │ ──► DynamicImage
│ (decode) │
└──────────────┘
│
▼
┌──────────────┐
│ to_rgba8() │ ──► Vec<u8> RGBA
└──────────────┘
│
▼
┌──────────────┐
│ wgpu::Queue │ ──► write_texture()
│ (upload) │
└──────────────┘
│
▼
GPU Texture (RGBA8Unorm)
input_texture (RGBA8Unorm)
│
▼
┌─────────┐
│ Height │ ──► height_texture (R32Float)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│ Normal │ ──► normal_texture (RGBA8Unorm)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│Metallic │ ──► metallic_texture (R8Unorm)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│Smoothness│ ──► smoothness_texture (R8Unorm)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│ Edge │ ──► edge_texture (R8Unorm)
│ Shader │
└────┬────┘
│
▼
┌─────────┐
│ AO │ ──► ao_texture (R8Unorm)
│ Shader │
└─────────┘
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
materialize-cli/
├── Cargo.toml
├── README.md
├── docs/
│ └── (documentação)
├── src/
│ ├── main.rs # Entry point
│ ├── cli.rs # CLI argument parsing
│ ├── pipeline.rs # Pipeline orquestração
│ ├── gpu.rs # GPU abstraction
│ ├── io.rs # Image I/O
│ ├── preset.rs # Material presets
│ └── shaders/
│ ├── height.wgsl
│ ├── normal.wgsl
│ ├── metallic.wgsl
│ ├── smoothness.wgsl
│ ├── edge.wgsl
│ └── ao.wgsl
└── tests/
└── integration_test.rs
| Crate | Versão | Propósito |
|---|---|---|
| wgpu | 29 | Compute shaders GPU |
| pollster | 0.4 | Runtime async blocking |
| image | 0.25 | Decode/encode de imagens |
| clap | 4.6 | CLI argument parsing |
| anyhow | 1.0 | Error handling |
| Crate | Versão | Propósito |
|---|---|---|
| tempfile | 3.27 | Arquivos temporários para testes |
- 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
- Direto: Sem precisar de render pipeline, vertex buffers, framebuffers
- Simples: Um shader = uma função de compute
- Eficiente: Sem overhead de rasterização
- 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
- 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
- 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