Skip to content

metal: honor Conf.sample_count for MSAA on iOS + matching pipelines#640

Open
benface wants to merge 3 commits into
not-fl3:masterfrom
benface:metal-msaa-support
Open

metal: honor Conf.sample_count for MSAA on iOS + matching pipelines#640
benface wants to merge 3 commits into
not-fl3:masterfrom
benface:metal-msaa-support

Conversation

@benface

@benface benface commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Three coupled changes that together light up MSAA on the iOS Metal backend.

1. create_metal_view calls setSampleCount: on MTKView

The sample_count argument was prefixed _ and silently dropped, so Conf.sample_count had no effect on iOS Metal. macOS already does this.

2. new_pipeline matches the view's sampleCount

A pipeline's sampleCount must match every render-pass color attachment it draws to. Reading the view's value at pipeline-creation time keeps the main drawable and render_target_msaa() targets compatible with the same pipeline.

3. new_texture creates D2Multisample textures when sample_count > 1

For RenderTarget access with sample_count > 1, the color texture has to be a D2Multisample with matching setSampleCount: and mipmapLevelCount = 1, and can only carry RenderTarget usage (not ShaderRead/ShaderWrite). The single-sample resolve texture for the same target is created separately by the caller (e.g. macroquad's render_target_ex) and passed in via new_render_pass_mrt's resolve_img slice — handled in #638.

Constraint

Every render-pass color attachment must use the view's sample count. Mixing single-sample and multisample targets in one app would require per-target pipeline states, which Metal allows but miniquad's "one pipeline per material" model does not currently express. Callers that opt into Conf.sample_count > 1 should use render_target_msaa() (or pass a matching sample_count) for every offscreen color target.

Builds on #634, #635, #636, #637, #638, #639.

Benoît Rouleau added 3 commits June 13, 2026 00:24
Three coupled changes that together light up MSAA on the iOS Metal
backend:

1. `create_metal_view` calls `setSampleCount:` on `MTKView` from
   `Conf.sample_count` (the param was previously prefixed `_` and
   silently dropped). macOS already did this.

2. `new_pipeline` reads the view's `sampleCount` and sets it on
   `MTLRenderPipelineDescriptor`. A pipeline's sampleCount must match
   every render-pass color attachment it draws to; using the view's
   value as the canonical app-wide count keeps the main drawable and
   `render_target_msaa()` targets compatible with the same pipeline.

3. `new_texture` for `RenderTarget` access with `sample_count > 1`
   creates the texture as `MTLTextureType::D2Multisample` with the
   matching `setSampleCount:`, `setMipmapLevelCount:1`, and
   `RenderTarget`-only usage. The single-sample resolve texture for
   the same render target is created separately by the caller
   (e.g. `render_target_ex` in macroquad) and passed in via
   `new_render_pass_mrt`'s `resolve_img` slice.

Constraint: every render-pass color attachment must use the view's
sample count. Mixing single-sample and multisample targets in one app
would require per-target pipeline states, which Metal allows but
miniquad's "one pipeline per material" model does not currently
express. Callers that opt into `Conf.sample_count > 1` should use
`render_target_msaa()` (or matching) for every offscreen color target.
`begin_pass` unconditionally set the color attachment's storeAction to
`MTLStoreAction::Store`. That clobbered:

  - MTKView's `currentRenderPassDescriptor`, which already wires a
    `resolveTexture` (and matching `MultisampleResolve` storeAction)
    on the color attachment when the view's `sampleCount > 1`.
  - The descriptor built in `new_render_pass_mrt` for offscreen MSAA
    targets, which has the same shape.

Both paths require `MultisampleResolve` — `Store` on a color attachment
with a resolve texture trips Metal validation:

    RenderPass Descriptor Validation
    MTLRenderPassAttachmentDescriptor resolveTexture must have
    storeAction of MTLStoreActionMultisampleResolve,
    MTLStoreActionStoreAndMultisampleResolve or MTLStoreActionUnknown

Inspect the attachment's `resolveTexture` and pick the storeAction
accordingly.
…eResolve)

The previous commit replaced an unconditional `Store` with
`MultisampleResolve` whenever the color attachment had a
resolveTexture, fixing the validation crash. But Metal allows
`MultisampleResolve` to discard the multisample texture's contents
after the resolve, and macroquad issues one Metal pass per draw call
with `loadAction = Load` so it can accumulate draws on top of a single
clear at frame start.

On the iOS 27 simulator the discarded contents are loaded back as
magenta, so every draw after the clear renders on top of magenta and
the frame ends up magenta-tinted everywhere except the pixels touched
by the very last draw call.

`StoreAndMultisampleResolve` both writes the resolved data to the
drawable AND preserves the multisample texture, so a subsequent
`Load` reads back the accumulated frame.
@benface benface force-pushed the metal-msaa-support branch from 246c371 to 7c74c42 Compare June 13, 2026 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant