Sample CodemacOSReviewed 2026-07-21View on Apple Developer

Training a neural network to render irradiance in real time

At a glance

Item Summary
Purpose Train a small neural network on the GPU to approximate diffuse irradiance, and compare the result against Monte Carlo integration and a pre-trained ML model.
App architecture Objective-C, Python host code with Metal shaders; offline Python training exports model weights, and the runtime renderer selects an irradiance technique that consumes them during Metal rendering.
Main patterns Offline/online split, Strategy, Renderer boundary
Scope High-level review of 18 scanned source files and 18 detected declarations; build assets are omitted.

Project structure

Source bundle/
├── train_irradiance.py
├── Application/
│   ├── ViewController.m
│   ├── main.m
│   ├── AppDelegate.h
│   └── AppDelegate.m
└── Renderer/
    ├── Composite.metal
    ├── Renderer.m
    ├── IrradianceModel.h
    └── ShaderTypes.h

Structure observations

  • The entry/composition boundary and renderer or operation boundary are separate in the source; IrradianceMLEncoderTechnique is the principal feature coordinator.
  • GPU-specific logic stays in Metal shader files; shared headers bridge host/shader layouts where present.
  • The tree above is intentionally pruned to composition, resource, and shader files.

Overall architecture

Reference code

train_irradiance.py:233 — offline model tracing and Core ML conversion

def main() -> None:
    # ...

Interpretation

This diagram crosses an offline/runtime boundary: Python produces model weights; it does not call the app renderer. At runtime, the irradiance ML technique consumes the exported representation and the renderer composites the result.

Ownership and state

Ownership evidence

Application/ViewController.m:35ViewController creates and stores _renderer

@implementation ViewController
// ...
    _renderer = [[Renderer alloc] initWithMetalKitView:_view];
// ...
@end
Owner Object or state Relationship Mutation authority
ViewController _renderer / Renderer Creates and stores; the diagram uses composition because construction is source-visible. The declaring scope performs setup and replacement.
IrradianceMLEncoderTechnique Feature-specific framework and Metal resources Operation-local calls or stored state; exclusive lifetime is not assumed beyond cited evidence. Feature setup/encoding code controls mutation and command submission.

Class and protocol design

Type Responsibility Depends on or conforms to Source
ViewController selects/configures the view and composes the feature objects. NSViewController Application/ViewController.h:10
Renderer owns pipeline/resource setup and per-frame command encoding. NSObject, MTKViewDelegate Renderer/Renderer.h:17
AppDelegate handles application/window lifecycle callbacks. NSObject, NSApplicationDelegate Application/AppDelegate.h:10
IrradianceTechnique defines the shared draw, resize, training-step, reset, and training-data interface for interchangeable irradiance techniques. MTL4CommandBuffer, textures, MTLTensor Renderer/IrradianceTechnique.h:12
IrradianceMonteCarloTechnique implements the Monte Carlo baseline with a compute pipeline and argument table; training operations are no-ops. NSObject, IrradianceTechnique Renderer/IrradianceTechnique.m:42
IrradianceMPPTechnique owns the learned irradiance model, trainer, pipeline resources, and per-frame/training implementation. NSObject, IrradianceTechnique, IrradianceTrainer Renderer/IrradianceTechnique.m:90

A local protocol/conformance boundary is present, so protocol-oriented collaboration is claimed only for that seam.

Access control

Symbol Access Verified effect Design reason
AppDelegate header-visible available to translation units that import the header; this is not Swift public. Keep the usable surface no wider than the collaboration requires. (Application/AppDelegate.h:10)
Renderer implementation details implementation-only native helpers, stored state, or registration code stays out of the imported header contract. Hide native implementation details from importing translation units. (Renderer/Renderer.m:1)
Irradiance entry points Metal library boundary host code resolves named shader entry points; Swift access modifiers do not apply. Expose only the compute kernels and tensor bindings required for runtime inference. (Renderer/Irradiance.metal:194)
train_irradiance Python module boundary the script is an offline training/export tool, not runtime app access control. Keep the usable surface no wider than the collaboration requires. (train_irradiance.py:1)

This sample does not use Swift private, fileprivate, or public for its native boundary. Objective-C/Objective-C++ use header versus implementation placement, C++ uses access specifiers/linkage, Python uses module conventions, and Metal entry points cross a compiled-library boundary; these are not Swift access levels.

Logic ownership and placement

Logic Owning type or file Why it lives there
Selects/configures the view and composes the feature objects ViewControllerApplication/ViewController.h:10 The type’s callbacks and stored state align with this responsibility.
Owns pipeline/resource setup and per-frame command encoding RendererRenderer/Renderer.h:17 The type’s callbacks and stored state align with this responsibility.
Handles application/window lifecycle callbacks AppDelegateApplication/AppDelegate.h:10 The type’s callbacks and stored state align with this responsibility.
Python training/export plus app inference IrradianceMLEncoderTechnique / train_irradiance.py:233 Keeps model training and conversion outside the runtime renderer.
Metal neural irradiance evaluation Renderer/Irradiance.metal:194 GPU-parallel inference remains in the Metal compilation boundary.

Shader boundary reference

Renderer/Irradiance.metal:194 — neural irradiance compute kernel and tensor bindings

kernel void irradianceMLPComputeShader(
    uint2 threadPos [[thread_position_in_grid]],
    uint  lane      [[thread_index_in_simdgroup]],
    uint  sgIdx     [[simdgroup_index_in_threadgroup]],
    texture2d<half> normalBuffer   [[texture(TextureIndexNormalBuffer)]],
    tensor<device half, dextents<int, 3>> irradianceOutput [[buffer(BufferIndexOutputTensor)]],
    tensor<device half, extents<int, NUM_INPUT, NUM_HIDDEN>>  layer0Weights [[buffer(BufferIndexMLPLayer0)]],
    tensor<device half, extents<int, NUM_HIDDEN, NUM_HIDDEN>> layer1Weights [[buffer(BufferIndexMLPLayer1)]],
    tensor<device half, extents<int, NUM_HIDDEN, MLP_OUTPUT>> layer2Weights [[buffer(BufferIndexMLPLayer2)]],
    const device half *bias0 [[buffer(BufferIndexMLPBias0)]],
    const device half *bias1 [[buffer(BufferIndexMLPBias1)]],
    const device half *bias2 [[buffer(BufferIndexMLPBias2)]])
{
    // ...
}

Design patterns

Pattern Source evidence Purpose or tradeoff
Offline/online split train_irradiance.py:233 The offline tool traces and converts the model; the runtime consumes the exported representation.
Strategy train_irradiance.py:26 A common caller selects between concrete rendering implementations.
Renderer boundary Renderer/Irradiance.metal:194 UI/lifecycle code composes a renderer while neural GPU execution stays in its dedicated shader boundary.

Naming conventions

  • Role suffixes make ownership visible: ViewController, Renderer, AppDelegate.
  • Method names describe setup or encoding actions: load_hdr, _dirs_to_uv, _sample_env, _cosine_sample_hemisphere, _uniform_sphere, _compute_irradiance, __init__.
  • Shader entry points use stage/operation names: fullscreenVertexShader, compositeFragmentShader, geometryVertexShader, geometryFragmentShader, irradianceMCComputeShader, textureToTensorKernel.
  • Names favor concrete domain roles and target-local types; no broad public library namespace is introduced.

Architecture takeaways

  • Keep Python training/export focused on composition; IrradianceMLEncoderTechnique is the owner of Python training/export plus app inference.
  • Treat Metal neural irradiance evaluation as a separate execution/compilation boundary with explicit resource and data-layout contracts.
  • The verified ownership edge is ViewController_renderer; broader exclusive ownership is not inferred.
  • Access-control rationale follows concrete language boundaries rather than translating every header or shader symbol into Swift terms.

Source map

Source file Architectural role
train_irradiance.py IrradianceMLP, _DecodedMLP
Application/ViewController.m ViewController
Renderer/Composite.metal MaterialParams
Application/main.m entry point or feature implementation
Renderer/Renderer.m Renderer
Renderer/IrradianceModel.h entry point or feature implementation
Renderer/ShaderTypes.h entry point or feature implementation
Application/AppDelegate.h AppDelegate
Application/AppDelegate.m AppDelegate