# How ArmorPaint Viewport Rendering Handles MSAA, Post-Processing, and Real-Time Brush Previews

> ArmorPaint viewport rendering uses GPU MSAA resolution, tone-mapping, FXAA post-processing, and immediate-mode draw calls for real-time brush previews and low-latency feedback.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: internals
- Published: 2026-09-14

---

**ArmorPaint resolves multi-sample anti-aliasing on the GPU via `gpu_resolve_msaa()` in [`util/render.c`](https://github.com/armory3d/armorpaint/blob/main/util/render.c), applies tone-mapping and FXAA post-processing, then composites real-time brush previews in [`viewport.c`](https://github.com/armory3d/armorpaint/blob/main/viewport.c) using immediate-mode draw calls to maintain low-latency cursor feedback.**

ArmorPaint's viewport rendering system balances visual fidelity with interactive painting performance through a carefully orchestrated GPU pipeline. The implementation spans [`viewport.c`](https://github.com/armory3d/armorpaint/blob/main/viewport.c), [`util/render.c`](https://github.com/armory3d/armorpaint/blob/main/util/render.c), and [`util/brush.c`](https://github.com/armory3d/armorpaint/blob/main/util/brush.c) to resolve MSAA, execute post-processing passes, and overlay dynamic brush cursor previews without CPU readbacks. This architecture ensures high-quality antialiasing while keeping brush feedback instantaneous.

## MSAA Rendering and GPU-Side Resolve

ArmorPaint begins each frame by rasterizing geometry into a multi-sampled render target configured according to user quality settings.

### Creating the Multi-Sampled Target

In [`render_path.c`](https://github.com/armory3d/armorpaint/blob/main/render_path.c), the primary color attachment is created with `GPU_TEXTURE_FORMAT_RGBA32` and the `GPU_TEX_FLAG_MSAA` flag. This enables hardware multi-sample antialiasing for all subsequent geometry passes, including deferred G-buffer fills and forward opaque or transparent draws.

### The Resolve Operation

After scene rasterization completes, [`util_render.c`](https://github.com/armory3d/armorpaint/blob/main/util_render.c) invokes the Iron-engine helper `gpu_resolve_msaa()` to convert the multi-sample buffer into a single-sample texture stored in `render_target->resolved`. This operation executes entirely on the GPU without CPU readback, preserving performance by avoiding costly host-device transfers.

```c
/* util/render.c – resolve MSAA and run post‑process passes */
gpu_resolve_msaa(rt->_image, rt->resolved);               // ← MSAA resolve
draw_begin(rt->resolved, true, 0);                       // start post‑process chain
apply_tonemap();                                         // tone‑mapping, exposure, etc.
apply_fxaa();                                            // optional fast AA pass
draw_end();                                              // finish and store final image

```

As shown in the code, the resolve step immediately precedes the post-processing chain, operating on `rt->_image` and outputting to `rt->resolved`.

## Post-Processing Pipeline

Following the MSAA resolve, [`util_render.c`](https://github.com/armory3d/armorpaint/blob/main/util_render.c) executes a series of full-screen passes to finalize the image before compositing.

### Tone-Mapping and Exposure

The pipeline applies tone-mapping and exposure adjustments via `apply_tonemap()`, processing the resolved texture to map high dynamic range values into displayable color space.

### FXAA Anti-Aliasing Pass

An optional fast approximate anti-aliasing (FXAA) pass is applied through `apply_fxaa()` to further refine edge quality. This runs after tone-mapping within the same `draw_begin`/`draw_end` block initiated on the resolved texture.

## Real-Time Brush Preview System

The brush preview operates as an overlay drawn after the 3D scene post-processing completes, ensuring it appears instantly without requiring additional resolves.

### Generating the Preview Texture in util/brush.c

When users modify brush parameters or move the cursor, [`util_brush.c`](https://github.com/armory3d/armorpaint/blob/main/util_brush.c) constructs a small preview mesh representing the brush radius and hardness. This geometry is rendered into a transient GPU texture assigned to `brush_preview` using immediate-mode draw calls.

```c
/* util/brush.c – update preview mesh */
brush_preview = gpu_create_texture(...);
draw_begin(brush_preview, false, 0);
draw_brush_shape(brush_radius, brush_hardness);
draw_end();

```

### Compositing in viewport.c

The [`viewport.c`](https://github.com/armory3d/armorpaint/blob/main/viewport.c) module retrieves the resolved render target from the render path (identified as `"last"`) and composites the brush preview on top using additive blending. This occurs each frame via `draw_image` calls that layer the base image followed by the brush overlay at cursor coordinates.

```c
/* viewport.c – draw final image with brush overlay */
render_target_t *rt = any_map_get(render_path_render_targets, "last");
gpu_texture_t   *final = rt->resolved;               // already MSAA‑resolved

draw_begin(g_context->output, false, 0);
draw_image(final, 0, 0);                             // base image
draw_image(g_context->brush_preview, cursor_x, cursor_y); // brush overlay
draw_end();

```

By deferring brush compositing until after the MSAA resolve and post-processing, the system avoids sampling artifacts on the cursor while maintaining full interactivity.

## Summary

ArmorPaint's viewport pipeline achieves high-quality rendering with responsive tool feedback through these key architectural decisions:

- **GPU-side MSAA resolve**: The `gpu_resolve_msaa()` function in [`util/render.c`](https://github.com/armory3d/armorpaint/blob/main/util/render.c) converts multi-sample buffers to single-sample textures without CPU readback, preserving fill rate performance.
- **Sequential post-processing**: Tone-mapping and FXAA operate on the resolved texture within the same render pass chain, ensuring consistent anti-aliasing across the final image.
- **Deferred brush compositing**: Real-time brush previews generated in [`util/brush.c`](https://github.com/armory3d/armorpaint/blob/main/util/brush.c) are overlaid in [`viewport.c`](https://github.com/armory3d/armorpaint/blob/main/viewport.c) after all 3D processing completes, preventing cursor lag and sampling artifacts.
- **Immediate-mode UI**: The use of `draw_begin`, `draw_image`, and `draw_end` calls enables efficient per-frame updates of brush position without reallocating render targets.

## Frequently Asked Questions

### Why is the brush preview drawn after the MSAA resolve?

Drawing the brush preview after `gpu_resolve_msaa()` ensures the cursor overlay appears sharp and undistorted by multi-sample reconstruction artifacts. Since the brush cursor is a UI element rather than scene geometry, it does not require MSAA and benefits from being composited as a single-sample overlay on the already-resolved final image, as implemented in [`viewport.c`](https://github.com/armory3d/armorpaint/blob/main/viewport.c).

### What post-processing effects does ArmorPaint apply in util_render.c?

According to the source code in [`util/render.c`](https://github.com/armory3d/armorpaint/blob/main/util/render.c), the pipeline applies **tone-mapping** via `apply_tonemap()` for exposure correction and high dynamic range mapping, followed by an optional **FXAA** (Fast Approximate Anti-Aliasing) pass via `apply_fxaa()` performed on the resolved single-sample texture.

### How does ArmorPaint maintain performance during the MSAA resolve?

ArmorPaint maintains interactive frame rates by executing the MSAA resolve entirely on the GPU using `gpu_resolve_msaa()`, which avoids CPU readback of the multi-sample buffer. This GPU-side operation preserves memory bandwidth and allows the subsequent post-processing and brush preview compositing to occur without stalling the rendering pipeline.

### Where is the brush preview texture stored between frames?

The brush preview texture is maintained in the global context as `g_context->brush_preview` and is regenerated in [`util/brush.c`](https://github.com/armory3d/armorpaint/blob/main/util/brush.c) whenever brush parameters change. This transient texture persists in GPU memory, allowing [`viewport.c`](https://github.com/armory3d/armorpaint/blob/main/viewport.c) to reference it directly during the final compositing pass without reconstruction each frame.