> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teriyakigaming.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rendering Pipeline

> How a page becomes pixels: layers to a Slate widget tree, the render-target bake, the ink texture, the MF_InkwellPageInk contract, and the page material that multiplies ink over paper

Inkwell does not paste a texture on a quad. Each page of an
[Ink Page Document](/plugins/inkwell/authoring/documents) (`UInkPageDocument`) is composed at runtime into
a Slate widget tree, baked into an **ink texture** — a transparent render target holding
nothing but the content — and the page material composites that ink over the paper. The
paper survives inside every stroke, white ink cannot brighten the sheet, and the result
reads as printing rather than a decal.

<Frame caption="A finished page as it bakes at runtime.">
  <img src="https://mintcdn.com/teriyaki-gaming/rfYUFiwAdsWmGZKv/images/inkwell/baked-page.png?fit=max&auto=format&n=rfYUFiwAdsWmGZKv&q=85&s=f45790d941365585ce4b3a31d0189339" alt="An Inkwell page baked at runtime, ink composited over paper" width="650" height="884" data-path="images/inkwell/baked-page.png" />
</Frame>

Almost all of this lives in one Blueprint function library, `UInkPageRenderLibrary`
(category **Inkwell → Page Content**), plus a world subsystem,
`UInkPageCacheSubsystem`.

## The pipeline

<Steps>
  <Step title="Compose the layer stack into a Slate widget tree">
    `BuildInkWidget` walks the page's [layers](/plugins/inkwell/authoring/layers) back to front and builds
    one overlay slot per **visible** layer — text, image or drawing. It takes a **Side**:
    a sheet's front and its back are two independent layer stacks, composed and baked
    separately.
  </Step>

  <Step title="Allocate a transparent ink target">
    `CreateInkTarget` makes a linear-space render target at the page's clamped size, with a
    fully transparent clear color.
  </Step>

  <Step title="Draw the widget into it, twice">
    `RenderPageToTarget` runs a gamma-free `FWidgetRenderer` over the tree. The result is
    the ink layer: content over transparency, no paper anywhere in it.
  </Step>

  <Step title="Feed the page material">
    A dynamic instance of the resolved page material receives the ink texture plus the
    document's paper and ink settings, and is assigned to a mesh slot (or handed to UMG as
    a brush). Which material that is depends on the document's Paper Mode — see
    [the page material](#the-page-material).
  </Step>
</Steps>

Because the ink layer never contains the paper, one document can be printed onto different
papers, tinted, or faded without re-baking anything.

## Composing the widget tree

`BuildInkWidget` produces a box sized to the page's clamped resolution, containing an
`SOverlay` with **one slot per visible layer, in array order**. Index 0 draws first (the
back of the page), the last entry draws last (the front). Nothing reorders or groups the
layers: where a Text layer sits in that stack is exactly what decides whether an image is a
watermark under the writing or a stamp over it.

Layers whose **Visible** flag is off are skipped entirely — by the bake and by the Page
Editor preview alike. Each layer's **Opacity** is applied as Slate *render opacity*, so it
reaches text and drawn strokes, not only images.

| Layer type  | What it becomes                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Text**    | An `SRichTextBlock` fed the layer's markup, using the layer's own Justification and its line spacing — the layer's **Line Height Percentage** when set, otherwise the document's. A layer whose **Size** is zero on either axis fills the page inside the document's **Margins Px** and word-wraps automatically. A layer with a real size is placed on a constraint canvas at its normalized Position and wraps at an explicit pixel width instead. |
| **Image**   | An `SImage` on a constraint canvas, anchored at the layer's normalized Position. Width comes from the normalized Size; a height of 0 or less keeps the texture's aspect ratio. Rotation pivots about the image's center, and the image's own Opacity multiplies its alpha.                                                                                                                                                                           |
| **Drawing** | An `SInkStrokeCanvas` painting the layer's strokes with Inkwell's five procedural brushes. It is told the document's **unclamped** page width, so stroke widths keep their proportions whether the page bakes at full resolution, bakes clamped down, or previews small.                                                                                                                                                                             |

Every position and size in a layer is **normalized page space** — (0,0) is the page's
top-left, (1,1) its bottom-right — so changing the page resolution rescales a layout
instead of breaking it. Boxes are resolved against the *clamped* page size, the same size
the widget itself is built at, so a document scaled down for the bake keeps everything
where the author put it.

Each Text layer gets **its own rich-text marshaller**. They are never shared: a marshaller
carries the dirty flag its text block re-marshals on, and two blocks sharing one would let
the first to paint clear the flag out from under the second, leaving it blank.

## The ink render target

`CreateInkTarget` produces a render target whose every property is load-bearing:

| Property     | Value                            | Why                                                                                                                                                                                                                                                                               |
| ------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Clear color  | Transparent                      | The transparent background is what lets the material composite ink over paper instead of pasting a sticker.                                                                                                                                                                       |
| SRGB         | `false`                          | Keeps the bake in linear space.                                                                                                                                                                                                                                                   |
| Target gamma | `1.0`                            | Same.                                                                                                                                                                                                                                                                             |
| Pixel format | Slate's recommended color format | Matches the Slate renderer — 8-bit RGBA on desktop.                                                                                                                                                                                                                               |
| Filtering    | Bilinear                         | —                                                                                                                                                                                                                                                                                 |
| Address mode | Clamp, both axes                 | On a custom mesh whose UVs stray outside 0–1, wrapping would tile the page into the overshoot and smear the text. Clamped, the overshoot repeats the transparent border pixel, the ink alpha goes to zero, and the material shows plain paper there — stray UVs degrade politely. |
| Mip chain    | None                             | The target is a single mip. See [page size](#page-size-and-what-it-costs).                                                                                                                                                                                                        |

The widget renderer runs **without gamma correction**, matching the target: the page
material samples the ink with a *LinearColor* sampler, so the stored values must be linear.

<Warning>
  Building your own page material? Sample the ink texture with a **LinearColor** sampler. A
  default sRGB sampler washes every color out by exactly one gamma curve — that is the cause
  of nearly every "my custom page material looks faded" report. `MF_InkwellPageInk` already
  samples correctly; this bites only materials that sample the ink themselves.
</Warning>

`RenderPageToTarget` is **game thread only**, and returns `false` without baking when
rendering is impossible: the app cannot ever render, the null RHI is active, Slate is not
initialized, or the target is missing.

<Info>
  The bake deliberately draws the widget **twice into the same target**. Slate's auto-wrap
  computes its wrap width from the geometry cached by the *previous* paint, and an offscreen
  widget has no previous paint — on a single draw, center- and right-justified text lays out
  against a stale wrap width and lands shifted off the page. The first draw primes the
  geometry cache, the second draws the correctly wrapped layout over it. Text layers with an
  explicit box size wrap at a known width and sidestep the problem entirely, which is why
  they are built that way.
</Info>

## The page material

Which material a page's dynamic instance derives from is decided by the document's
**Paper Mode** (`EInkPaperMode`), resolved by `ResolvePageBaseMaterial`:

| Paper Mode                               | Base material                                                                                                                                                                                                          | What happens to the slot                             |
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| **Texture asset** (`Texture`)            | **Page Ink Material** from [Project Settings](/plugins/inkwell/reference/settings#materials) — by default `/Inkwell/Materials/MI_PageInk`, an instance of `M_PageInk`.                                                 | Replaced by the page instance.                       |
| **Generated** (`Procedural`)             | Same — the generated sheet arrives as the paper texture.                                                                                                                                                               | Replaced by the page instance.                       |
| **Custom Material** (`CustomMaterial`)   | The document's own **Paper Material** — your graph, carrying `MF_InkwellPageInk`.                                                                                                                                      | Replaced by an instance of the document's material.  |
| **Mesh's Own Material** (`MeshMaterial`) | Whatever material the target slot **already wears**, instanced per mesh instance — per-instance textures and parameter overrides all survive. A MID already in the slot is driven in place, never wrapped or replaced. | Never replaced. Only the ink parameters are written. |

There is no hard-coded content path anywhere in the runtime. `M_PageInk` is an opaque,
lit material: the composite goes into Base Color, so a page takes the scene's lighting
like the physical object it is pretending to be. `M_PageComposite_Preview` is its unlit
twin, writing the same composite into Emissive; the Page Editor uses it so the authoring
preview matches the game exactly.

### The MF\_InkwellPageInk contract

`/Inkwell/Materials/MF_InkwellPageInk` is a material function and the public contract
shared by **Custom Material** and **Mesh's Own Material** modes: drop it into any material
graph — wood desk, aged vellum, an animated hologram — and that material can receive
Inkwell's ink.

* **Input**: `PaperColor` — whatever your graph considers the paper.
* **Outputs**: `InkedColor` (your paper with the ink composited over it — wire it onward
  to Base Color or wherever your final color goes), `RoughnessLerpAlpha`, `InkCoverage`,
  `BackFace`, `PageUV` and `BackUV` (the face-mapped UVs, for sampling your own paper
  textures in register with the writing).
* The **ink** side of what Inkwell drives at runtime — the ink render targets, the UV face
  mapping, the ink and paper tints, ink strength and the back-face gates — lives **inside**
  the function, and UE hoists a function's parameters onto the calling material. Those nine
  names arrive on your material for free.

The **paper** side does not, and that is deliberate: the function inks whatever paper colour
you feed it, so the six paper parameters (`PaperTexture`, `UsePaperTexture`,
`BackPaperTexture`, `UseBackPaperTexture`, `BackPaperTint`, `HasDistinctBackPaper`) are
declared by the stock material rather than the function. Declare them in your own material
only if you want Inkwell to drive your paper — see
[Using your own page material](/plugins/inkwell/reference/settings#using-your-own-page-material).

The shipped `M_PageInk` and `M_PageComposite_Preview` are themselves built on the function,
so the stock materials keep the ink contract honest.

<Warning>
  In **Mesh's Own Material** mode, a target material *without* `MF_InkwellPageInk` (or the
  named ink parameters) shows **no ink and raises no error — that is the contract, not a
  failure**. The mesh keeps rendering exactly as it did; Inkwell writes parameters the
  material does not have, and they go nowhere. If a page in this mode shows no writing,
  the material is the first thing to check.

  A **Custom Material** document is stricter, because the document *names* the material: if
  the assigned Paper Material has no ink texture parameter, Inkwell logs one `LogInkwell`
  warning per material naming it and saying to drop `MF_InkwellPageInk` into its graph.
</Warning>

### What gets written

When Inkwell applies a page it creates (or reuses) a dynamic instance and pushes these
parameters. The parameter *names* are configurable in
[settings](/plugins/inkwell/reference/settings#material-parameters); the defaults are shown.

| Parameter                                        | Set from                                                                                                                                                                                                                                                                              |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `InkTexture`                                     | The baked ink render target for this page and side.                                                                                                                                                                                                                                   |
| `InkTint`                                        | The document's **Ink Tint** — a vector multiplied over everything written on the page.                                                                                                                                                                                                |
| `InkStrength`                                    | The document's **Ink Strength**. 1 is fully opaque ink; lower lets more paper through every stroke.                                                                                                                                                                                   |
| `PaperTint`                                      | The document's **Paper Tint** — or the back's own Tint when the side being applied is a back with distinct paper.                                                                                                                                                                     |
| `PaperTexture`                                   | The side's resolved paper — the page's **Paper Override** if set, otherwise generated paper when the mode is Generated, otherwise the document's **Paper Texture**. Set only when a paper actually resolves.                                                                          |
| `UsePaperTexture`                                | `1` when a paper texture resolved, `0` when none did.                                                                                                                                                                                                                                 |
| `InkUVTransform`, `InkUVOffset`, `InkOnBackFace` | Reset to the identity face mapping on **every** apply — MIDs are reused across page turns, and a mapping surviving from a previous owner would silently displace the new page. A component with a real **Page Face** mapping applies it afterwards via **Apply Face Mapping Params**. |
| `HasAuthoredBack`, `HasDistinctBackPaper`        | Reset to 0 on every apply, same discipline; raised again by the back-of-sheet pass when the page earns them.                                                                                                                                                                          |

Two deliberate asymmetries:

* With no paper texture, Inkwell leaves the *texture* parameter alone — the material keeps
  its own default — and only flips the switch to 0, so the material falls back to its flat
  paper color instead of being handed something arbitrary.
* Under **Custom Material** (with a material actually assigned) and **Mesh's Own
  Material**, the paper parameters (`PaperTexture`, `UsePaperTexture`) are **not written at
  all**. Those materials bring their own paper, and a customer graph that happens to use
  the same parameter names is never stomped.

<Note>
  A page with **no Document assigned** does not go through this path at all. The display
  component instances the page material, puts the **Blank Ink Texture** from settings in the
  ink slot and forces `UsePaperTexture` to 0, so a bare sheet shows blank paper rather than
  whatever ink was in that slot before.
</Note>

## Two-sided pages and the back of the sheet

A page can carry an independently authored back (`BackLayers` — see
[Layers](/plugins/inkwell/authoring/layers)). The back is a second, separate bake:

* **An unauthored back costs nothing.** The cache returns null for it before any
  allocation — no bake, no render target. Every document from before backs existed
  behaves exactly as it always did.
* **An authored back bakes its own ink target** at the same clamped size, cached under its
  own key (see [the bake cache](#the-bake-cache)).

What the *underside of the mesh* shows depends on the mesh:

* **The bundled sheet** (`SM_PageSheet`) is a thin slab with `Front`, `Back` and `Edge`
  material slots; its back face is U-mirrored. Inkwell dresses any mesh with a material
  slot named `Back`: an **authored back always wins** and is applied there as a full page
  material of its own. With no authored back, the back shows the front's ink as a
  **mirrored show-through** — like ink through thin paper — when the Page Face mapping's
  **Ink On Back Face** is on (the default), or blank paper in the sheet's stock when it is
  off.
* **A flat single-slot mesh** (the old plane, most custom props) has one material for both
  faces. An authored back still displays, through the `Back*` material parameters and
  `TwoSidedSign` selection inside `MF_InkwellPageInk`, sampled U-mirrored so plane and
  slab display backs identically. On a one-sided material, back faces are culled and the
  whole path folds to zero.
* The back's **paper** can differ from the front's: the document's **Back Paper** is Same
  As Front by default, or its own texture, its own generated sheet, or its own Custom
  Material. See [Paper](/plugins/inkwell/authoring/paper).

## Why it reads as ink, not a decal

Four decisions, all of them in the list above, add up to the effect:

1. **The bake contains only content.** There is no paper in the ink texture — the
   background is transparent, and the alpha is the coverage of the letterforms and strokes.
2. **The material composites, it does not replace.** Ink is laid over the paper by its own
   alpha, so the paper's grain, its ruling and its punched holes survive *inside* every
   stroke. White ink cannot brighten the sheet, because there is nothing to brighten with —
   uncovered pixels are paper.
3. **Ink Strength is a real dial.** Dropping it below 1 lets more paper through every mark
   at once, which is exactly how faded pencil and worn print behave.
4. **The composite is base color on a lit material.** The page shades with the room. An
   unlit overlay pasted on top would sit flat and bright wherever the paper went dark, and
   that is the single thing that most reads as a sticker.

## The bake cache

Baking is not free, and many things in a level may show the same page — a
[stack](/plugins/inkwell/components/page-stack) and a [display](/plugins/inkwell/components/page-display) of the same
journal, or five copies of the same note. In **game and PIE worlds** they all share bakes
through `UInkPageCacheSubsystem`, a world subsystem holding baked ink render targets.

* **Key**: document path + page index + the document's **Content Revision**; a back bakes
  under the same key with a side marker, so front and back are separate entries.
* **Scope**: one cache per world, Game and PIE only. Editor worlds have no cache subsystem
  and bake on demand.
* **Population**: lazy. A page is baked the first time something asks for it, so a 40-page
  book costs nothing until it is read — and a back costs nothing until someone flips the
  sheet over.

### What invalidates it

`ContentRevision` is a transient counter on the document. It is **not** an eviction
signal — it is part of the cache key, so bumping it means nothing will ever find the
old entries again.

| Action                                                                                 | Bumps Content Revision?                                    |
| -------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Any edit to the document asset in the editor (`PostEditChangeProperty`)                | Yes                                                        |
| **Add Page**, **Remove Page**, **Set Page**, **Set Page Markup**, **Set Layer Markup** | Yes                                                        |
| **Set Page Layers**, **Add Page Layer**, **Remove Page Layer**, **Move Page Layer**    | Yes                                                        |
| **Bump Revision**, called yourself                                                     | Yes                                                        |
| Writing into a struct you pulled out with **Get Page**                                 | No — the struct is a copy. Push it back with **Set Page**. |

<Warning>
  Bumping the revision orphans the old render targets but does not free them; they stay
  resident until the world ends. A long session that rewrites documents repeatedly — a
  notebook the player writes in, a log that appends — should call **Clear Page Cache** with
  that document to reclaim the memory. One or two rewrites are not worth the call.
</Warning>

**Clear Page Cache** (`ClearPageCache`) drops this world's cached bakes for one document,
fronts and backs alike. The subsystem's own **Clear Cache** with no document drops
everything in the world. Neither re-bakes: call **Refresh** on the displaying component
afterwards.

## Page size and what it costs

**Page Size Pixels** on the document is the bake resolution *and* the layout's coordinate
space. Every ink target and draw size in the pipeline derives from `GetClampedPageSize`:

* Neither axis may exceed **Max Page Bake Dimension**
  ([Project Settings](/plugins/inkwell/reference/settings#rendering), default **4096**).
* The clamp is **proportional** — one scale factor on both axes — so the aspect ratio
  survives and a clamped bake still fills its target instead of cropping.
* The floor is 2 × 2 pixels.
* The Page Editor's **Size px** boxes accept 128–4096 per axis in steps of 64, so in
  practice the default clamp only bites for documents built by script.

An ink target is 4 bytes per pixel with no mips:

| Page Size Pixels      | Ink target, per baked page side |
| --------------------- | ------------------------------- |
| 512 × 700             | ≈ 1.4 MB                        |
| 1024 × 1400 (default) | ≈ 5.5 MB                        |
| 1536 × 2100           | ≈ 13 MB                         |
| 2048 × 2800           | ≈ 22 MB                         |
| 4096 × 4096           | ≈ 67 MB                         |

That is **per page side actually baked**, and cached bakes accumulate as the player reads.
A 20-page journal at the default size costs about 110 MB once it has been read cover to
cover — authored backs the player has flipped to add one target each on top. At
2048 × 2800 the same journal costs about 460 MB, which is the number that decides this
setting on console.

<Warning>
  The ink target has **no mip chain**. Baking far above the size the page occupies on screen
  does not just waste memory, it makes distant pages shimmer as the bilinear filter
  undersamples the letterforms. Oversizing is a real cost in image quality, not only in
  bytes.
</Warning>

## Picking Page Size Pixels

Do it **before you author**, not after. Positions and box sizes for images, text boxes and
stroke points are normalized and survive a resize, but everything authored in absolute page
pixels does not: **Margins Px**, **Default Font Size** (and the per-run sizes in the
markup), each stroke's **Width Px**, and the procedural paper's
**Spacing / Thickness / Top margin / hole** values.
Double the page resolution after writing a page and the text keeps its pixel size while the
page grows around it — the writing effectively shrinks.

<Steps>
  <Step title="Choose the shape first">
    The aspect ratio of Page Size Pixels *is* the physical shape of the paper —
    `GetPageSizeCm()` derives the height from it and **Page Width Cm**. A4 is 1 : 1.414
    (1024 × 1448); US Letter is 1 : 1.294 (1024 × 1325); the default 1024 × 1400 sits
    between them. Landscape pages, postcards and torn scraps are other ratios.
  </Step>

  <Step title="Estimate the page's on-screen size at its closest">
    By default the [Ink Page Reader](/plugins/inkwell/components/reader) holds a page at whatever distance
    makes it cover **View Fill Fraction** (0.75) of the viewport's height — so a held
    page's on-screen height is roughly three quarters of your vertical resolution
    regardless of the page's physical size: about **800 px tall at 1080p, 1600 px at 4K**.
    A raised View Fill Fraction, or a low fixed **View Distance** with the fit turned off,
    raises the number.
  </Step>

  <Step title="Take the next size up, and stop">
    The default 1024 × 1400 already matches that reading view comfortably at 1080p. Go to
    1536 or 2048 only when the page is genuinely larger on screen — a page read at 4K in a
    full-screen UMG layout, a map the player zooms into, an edge-to-edge fill fraction.
    Do not raise it "for quality" on a note read at arm's length; see the mip warning
    above.
  </Step>

  <Step title="Sanity-check the text size">
    Default Font Size is 28 page pixels, and the default line height multiplier is 1.18. On
    the default 1024 × 1400 page that fits roughly 35 lines between the 90 px margins. If
    you change the page resolution, scale Margins Px
    and Default Font Size by the same factor to keep the page looking the way it did.
  </Step>
</Steps>

## Generated paper resolution

When **Paper Mode** is Generated, the paper is rasterized by the paper generator rather
than loaded from an asset. It is sized from the **clamped page size** — the same size the
ink bakes at — on purpose: paper of a different size would be resampled against the ink,
which is how a 2 px ruling turns into a soft gray band.

The sheet is an uncompressed BGRA8 transient texture, sRGB, never streamed, with a
**mip chain box-filtered in linear space** on the way out. The mips are why a ruled page a
few meters away still reads as ruled paper instead of aliasing into noise, and the linear
averaging is why the ruling does not darken as it shrinks.

Caching is by **hash of the settings plus the size**, and the cache lives on the *document*,
not on each display — so twenty pages of one journal share a single generated sheet, and
so do twenty copies of the note placed around a level. A back with its own generated sheet
gets a second cached texture, and dedupes against the front's when the settings match.
Change any procedural paper value and the hash changes, so the next request regenerates.
Memory is about 1.33 × the flat size: roughly **7.6 MB** for a 1024 × 1400 sheet, once per
document.

<Tip>
  A per-page **Paper Override** beats everything, generated or not. Setting one on a single
  page does not disturb the shared generated sheet the other pages use.
</Tip>

## The UMG path skips the bake

The [Ink Page widget](/plugins/inkwell/components/page-widget) (`UInkPageWidget`, shown in the palette as
**Ink Page**) does **not** bake to a render target. It builds the very same widget tree with
`BuildInkWidget` and puts it straight on screen inside a scale box, over a paper image, so
an on-screen page and an in-world page are built from identical code.

Consequences worth knowing:

* No render target is allocated and nothing is cached — the page rebuilds when the widget
  synchronizes, when you call **Refresh**, or when you turn a page.
* **Show Paper** draws the document's resolved paper behind the writing, tinted by Paper
  Tint. With no paper texture it falls back to a flat sheet in that tint.
* **Apply Ink Tint** reproduces the material's ink handling the only way Slate can: the
  document's Ink Tint with its alpha multiplied by Ink Strength, applied to the whole ink
  subtree.
* The page keeps its authored aspect ratio inside whatever slot you give it.
* The widget shows **fronts only** — page backs are a held-sheet feature of the
  [reader](/plugins/inkwell/components/reader) and the world materials.

## Reading the diagnostics: LogInkwell

Everything Inkwell has to say about a misconfigured page goes to the **`LogInkwell`**
log category. When a page is blank, the wrong size, or missing its paper, filter the
Output Log by `LogInkwell` before changing anything. The warnings you may meet:

| Warning says                                                                                                                                        | It means                                                                                                            |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| *"has no mesh to draw onto — set Target Mesh, add a mesh component to the actor, or enable Procedural Paper"*                                       | A display component has a document but nothing to draw it on.                                                       |
| *"Procedural Paper needs the actor to have a root component to attach the sheet to"*                                                                | The fallback sheet had nowhere to attach.                                                                           |
| *"paper material '…' has no 'InkTexture' parameter, so pages on it show no writing. Drop the MF\_InkwellPageInk material function into its graph."* | A Custom Material document names a material that cannot receive ink. Once per material.                             |
| *"no page ink material is configured"* / *"could not be loaded"* — naming a setting                                                                 | A [Project Settings](/plugins/inkwell/reference/settings) asset is unset or missing. Once per session, per setting. |
| *"editor preview bake kept failing after startup"*                                                                                                  | See [editor preview](#editor-preview) below.                                                                        |

Mesh's Own Material mode is the deliberate exception: a target material without
`MF_InkwellPageInk` shows no ink and logs nothing — that silence is the mode's contract.

## Editor preview

Outside of play, the display components preview pages in the level viewport through the
*same* bake path, with different render-target bookkeeping because editor worlds have no
cache:

* **Ink Page Display** bakes into **one reused transient render target** per component,
  reallocated only when the clamped page size changes — plus a second one for the page's
  back, allocated only once the page has an authored back.
* **Ink Page Stack** keeps **one render target per sheet**, so a fanned pile previews
  without churning allocations.

Previews are cosmetic by design. Applying a preview material never leaves the level package
dirty, and dynamic material instances are never saved into a map — previews rebuild
whenever the component registers, a property changes, or the map reopens.

<Note>
  The sheet spawned by **Procedural Paper** gets query-only collision that blocks **only the
  Visibility channel** — it deliberately does not intercept custom trace channels (AI sight,
  hitscan weapons). If your own interaction system traces a custom channel at pages, set
  that response on the spawned sheet yourself (**Get Procedural Paper Mesh** → Set Collision
  Response To Channel), or trace Visibility.
</Note>

<Accordion title="A page is blank right after the editor starts">
  A component can register before the editor's Slate renderer is usable, so the first preview
  bake fails — and because dynamic material instances never serialize, the sheet would keep
  the level's saved null material override and stay on the checkerboard forever.

  Inkwell retries on the core ticker, up to **8 attempts with exponential backoff** (0.25 s
  doubling to 32 s, about a minute in total), and resets the counter whenever a bake
  succeeds. Normally the page pops in a moment after startup. If it keeps failing you get a
  `LogInkwell` warning naming the actor and saying the sheet will refresh next time it is
  edited or the map is reopened. Editing any property on the component forces a fresh
  preview. Persistent failure means rendering is genuinely unavailable (null RHI) or the
  page ink material cannot be loaded — check the Output Log for the settings warning.
</Accordion>

## The bake API from Blueprint

Everything below is on `UInkPageRenderLibrary`, category **Inkwell → Page Content**. Full
signatures are in the [Blueprint API reference](/plugins/inkwell/reference/blueprint-api#ink-page-render-library).
Nodes that take a **Side** default it to Front.

| Node                          | What it does                                                                                                                                                                                                                                                   |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Get Page Ink Texture**      | The cached ink target for one side of a page, baking it on first request. Returns nothing for an unauthored back.                                                                                                                                              |
| **Apply Page To Mesh**        | Resolves the page's base material by Paper Mode, instances it, feeds it ink and paper, and assigns it to a mesh slot. Fronts only: it never touches a `Back` slot — the display components dress the back themselves, through the C++-only `ApplyBackOfSheet`. |
| **Bake Page To New Target**   | Bakes one side into a fresh, uncached render target you own.                                                                                                                                                                                                   |
| **Bake Page To Target**       | Bakes one side into a render target you already have.                                                                                                                                                                                                          |
| **Create Page Material**      | The page material instance for one side, without touching a mesh — UMG, decals, custom setups.                                                                                                                                                                 |
| **Get Page Ink Brush**        | A Slate brush of one page's front ink, for UMG Image widgets.                                                                                                                                                                                                  |
| **Apply Face Mapping Params** | Writes a **Page Face** mapping into a page material instance — call it after any apply, since applying resets the mapping to identity.                                                                                                                         |
| **Clear Page Cache**          | Drops this world's cached bakes for a document, fronts and backs.                                                                                                                                                                                              |

C++ additionally gets the pieces the components are built from: `GetClampedPageSize`,
`BuildInkWidget`, `CreateInkTarget`, `RenderPageToTarget`, `ApplyPageMaterialParams`,
`ResolvePageBaseMaterial`, `ApplyBackOfSheet` and `GetPageBaseMaterial`.

## Next

<CardGroup cols={2}>
  <Card title="Project Settings" icon="sliders" href="/plugins/inkwell/reference/settings">
    The page material, the fifteen parameter names, and Max Page Bake Dimension.
  </Card>

  <Card title="Paper" icon="newspaper" href="/plugins/inkwell/authoring/paper">
    The four paper modes from the authoring side, generated stock included.
  </Card>

  <Card title="Blueprint API" icon="code" href="/plugins/inkwell/reference/blueprint-api">
    Every function on the render library and the cache subsystem.
  </Card>

  <Card title="FAQ" icon="circle-question" href="/plugins/inkwell/reference/faq">
    Blank pages, stretched text and missing paper, diagnosed.
  </Card>
</CardGroup>
