Skip to content
The CNA BibleCNA 0.1.0-alpha.1 Edition

Chapter 19 The Renderer Contract

Every renderer family implements the same internal C++ contract, but the contract is deliberately permissive. Some methods are pure virtual; others return null, throw, forward to a reduced operation, or do nothing. Consequently, reaching an interface method is not evidence that the selected family performed the requested work. This chapter defines that shared boundary. Chapters 2032 own family-specific mechanisms and results; Appendix B owns the complete family inventory.

Evidence.  Interface declarations, factory routes, and shared public forwarding are source-proven at 1bb2145d. Several cross-family observations below come from a retained fourteen-product audit. Those rows are historically recorded where they were not rerun for this edition. The removed ASCII identity appears only when its historical result explains the current AsciiPostProcessEffect; omission of a newer family means “not measured by that cohort,” not “unsupported.”

19.1 Contract shape and default behavior

The contract is one header: modules/graphics/include/CNA/Internal/Renderers/Common/IGraphicsRenderer.hpp. Eleven interfaces divide resource operations from device-wide dispatch:

Interface Responsibility
IVertexBufferRenderer vertex upload, update hints, and declaration
IIndexBufferRenderer 16- and 32-bit index upload
ITextureRenderer 2D texture data, interop, and optional CPU-shadow behavior
ITexture3DRenderer / ITextureCubeRenderer volume and cube storage
IRenderTargetRenderer / IRenderTargetCubeRenderer target binding, applied MSAA, and attachment reporting
IEffectRenderer renderer-specific program compilation, binding, uniforms, and textures
IOcclusionQueryRenderer query begin/end/completion/sample count
ISpriteBatchRenderer batch state and the three renderer draw forms
IGraphicsRenderer device lifecycle, factories, state, targets, draws, presentation, readback, and capability queries

The default bodies form five distinct contracts:

Default Representative routes Caller-visible consequence
Pure virtual required 2D texture, SpriteBatch, vertex-buffer, and index-buffer factories A family must implement the route to compile.
Null factory volume/cube textures, render targets, effects, and occlusion queries Public construction may still produce an object with no private implementation.
No-op state setters, SpriteBatch options, several uniform setters Stored public state or a successful call may have no native effect.
Reduced fallback effect-aware draws may call a colored draw Work can complete after effect parameters were discarded.
Throw/refusal backbuffer readback, instancing, selected upload paths Failure is explicit, although exception type and timing vary.

Current contract.  Treat every optional method as an independent capability. A non-null renderer object, a true capability answer, and one successful draw establish different facts. Appendix G defines the evidence labels used to keep those facts separate.

19.1.1 Resource ownership and device tracking

A public GraphicsResource owns its renderer handle, normally through unique_ptr; Texture2D uses shared_ptr. The device owns neither. It records raw GraphicsResource addresses so it can dispose registered resources before destroying the renderer. GraphicsDevice::Dispose() moves and clears that list, calls each resource’s virtual Dispose(), raises Disposing, and then releases the renderer. Re-entrant deregistration therefore does not invalidate the iteration.

The registry follows object addresses, not renderer-handle ownership. The GraphicsResource copy and default move constructors carry the device pointer without registering the destination or replacing the source address. A Texture2D copy has its own disposed flag and shares the private handle; moving buffers, textures, or render targets can leave the device tracking the moved-from value. If a surviving untracked object outlives device disposal, its renderer resource may be released after the native context.

Limitation.  Keep live graphics resources address-stable and device-scoped. Prefer non-relocating ownership, do not move them across devices, and dispose every Texture2D alias before the device. The pin has no located test for copy/move followed by device-first teardown. The sibling docs/graphics-resource-lifetime.md is stale where it says destructor cleanup suppresses ResourceDestroyed; the current implementation still calls that device hook.

Chapter 12, §12.8, covers event ordering and the remaining constructor/destructor gaps. Figure 14.1 summarizes the ownership relation.

19.1.2 Null factories and delayed failure

Null factory results do not share one public failure policy:

Private result Public result First use at the pin
Null occlusion query Constructed OcclusionQuery Begin/end do nothing; completion is false and count is zero.
Null volume/cube texture Policy depends on the public wrapper An honest Texture3D capability answer rejects construction; an optimistic answer can still produce a null object. Cube and volume transfers now throw instead of fabricating or discarding content.
Null 2D/cube target Constructed target with no cached target renderer Sampling and transfer operations reject missing storage; target binding throws before public binding state changes.
Null effect Renderer-dependent public failure Chapter 17 distinguishes null, invalid, throwing, and working routes.

Software supplies 2D targets and cube-texture storage but still inherits the null factories for volume textures, cube targets, and occlusion queries; several 2D families inherit a wider subset. D3D9 can also return null according to device capabilities. A cube or volume texture passed to ShaderEffect remains a sharp edge because its CNAEXT renderer accessor assumes that the private owner exists. Construction alone therefore does not establish usable storage. No individual capability value covers every texture and target factory.

The framework needs one consistent source-level policy—early NotSupportedException, or explicit nullable availability plus guards throughout. This book records the current behavior and does not choose or implement that policy.

19.1.3 Borrowed native handles

GetWindowInternal() and GetRendererInternal() are pure virtual because the shared game path needs a window, even though not every family has one. The returned pointers are borrowed:

  • ordinary windowed families return their current SDL_Window*;

  • HEADLESS, SOFTWARE, and D3D12 HeadlessEXT return null;

  • only SDL_RENDERER returns its own SDL_Renderer*; other families normally return null for that method;

  • GameWindow::GetNativeSdlWindowEXT() and the integer-shaped XNA Handle property expose the same window without transferring ownership.

Do not call SDL_DestroyWindow on either result or cache it across device disposal. Explicitly disposing the game-owned device does not invalidate the separate GameWindow wrapper, so later use of that wrapper can reach a destroyed SDL object.

PresentationParameters::DeviceWindowHandle is the inverse borrowed route. A nonzero value is reinterpreted as SDL_Window*; CNA does not validate or destroy it. The pinned implementation publishes an attached window to the global mouse and text-input services and, at teardown, clears either handle only if it still names that window. An embedding host must keep the window alive through renderer teardown. The global slots still have last-writer semantics, so two live devices can replace one another’s input target; the located tests do not cover that multi-device lifecycle.

19.2 Presentation and input are family contracts

The public manager stores one presentation request. The selected family determines whether that request changes a compositor, a swapchain, a CPU buffer, only reported state, or nothing.

19.2.1 Input-coordinate routing

Window-to-logical input uses three tiers: an associated SDL renderer; otherwise a static SDL_Window* to IGraphicsRenderer* registry; otherwise raw coordinates. The inverse mouse-warp route uses the same ordering. The compact matrix below names the current behavioral classes; family chapters retain the implementation detail.

Family group Route Boundary
SDL_RENDERER SDL logical-presentation conversion Implements all five modes, including offsets; public read-side and direct write-side tests exist.
EasyGL / Canvas Static renderer registry Fixed-height scaling is implemented; other stored modes lack matching offset/output behavior.
SDL_GPU / WEBGPU Static renderer registry Five-mode math uses physical pixels while events and warps use window coordinates; high pixel density can mis-scale input.
FREEDIRECT Associated private SDL renderer wins Mapping appears after first present and is hard-coded to letterbox; direct CNA transform overrides are shadowed.
Vulkan, BGFX, Direct3D families Raw fallback Either presentation remains physical or the stored virtual request is not implemented as an output transform.
SOFTWARE / HEADLESS No window route Logical coordinates belong only to internal buffers or trace state.

An immediate Mouse::SetPosition/GetState test is weak because the setter first writes the requested logical value into InputManager; that round trip can pass even if the physical warp is wrong. The strongest located public evidence injects physical SDL motion into a scaled presentation and observes the converted logical point.

19.2.2 Window-registry lifetime

The second tier is one process-wide, unsynchronized unordered_map<SDL_Window*, IGraphicsRenderer*>. Registration overwrites by key and unregistration erases by window alone; neither operation checks ownership. EasyGL registers before several fallible constructor steps, so an exception can leave a dangling value. Two devices attached to the same borrowed window create another failure: the second replaces the first, then destruction of the first removes the second’s entry. Address reuse and concurrent lookup add equivalent hazards.

This requires an ownership and thread-affinity decision in CNA. Suitable designs include an RAII registration after fallible setup, owner-checked removal, and either rejecting duplicate devices or defining multi-device routing. No located test covers constructor failure after registration, duplicate attachment, window-address reuse, or concurrent access.

19.2.3 Presentation modes

The CNAEXT PresentationMode property belongs to GraphicsDeviceManager. Direct device construction leaves the renderer-create argument at its fixed-height default. The manager route applies the mode before reset so implementations that derive a virtual width see the intended value.

Implementation group Current behavior Evidence scope
SDL_RENDERER Maps all five requests to SDL logical presentation; fixed height derives width. Source and focused routing tests; no five-mode pixel corpus.
SDL_GPU / WEBGPU Computes native, fixed-height, stretch, letterbox, and overscan rectangles. Complete local geometry; visual coverage is partial.
EasyGL / Canvas Derives fixed-height width; other values retain dimensions without implementing bars or crop. Source plus fixed-height coverage; browser output remains unverified.
Vulkan / BGFX / Direct3D Stores, ignores, or partially reports the request while rendering to physical output. Source and renderer-specific smoke evidence; not a portable five-mode contract.
Software / Headless Resizes or simulates an internal grid. No display compositor exists, so bar/crop semantics do not apply.
FREEDIRECT Stores the enum; the presenter uses letterbox and has a later-resolution defect. Exact pixels for selected paths, not five policies.

Portable code should configure the manager before normal initialization and use NativeBackBuffer when physical readback coordinates matter. An enum round trip is not evidence that letterbox, crop, or stretch reached output.

19.2.4 Swap interval

Public conversion preserves Immediate=0, One=1, and Two=2, but families interpret them differently. The manager’s Boolean retrace property reaches only 0 or 1; 2 requires explicit PresentationParameters. Because Game constructs its first renderer before derived-game configuration, a family that consumes only the constructor argument can remain at the default interval.

Group 0 / 1 / 2 mapping Qualification
EasyGL Passes the exact integer at construction and reset. Off/on is runtime-observed through GL state; interval 2 is not timing-verified.
Vulkan Immediate-or-mailbox / FIFO / FIFO-relaxed-or-FIFO at construction. Runtime hook is a no-op; FIFO-relaxed is adaptive tearing, not half-refresh pacing.
BGFX, WebGPU, SDL GPU, D3D11/12 Collapse positive values to enabled VSync or FIFO. 2 is equivalent to 1 despite some downstream APIs supporting wider intervals.
D3D9 Maps all three to D3D9 intervals and resets. Windowed D3D9 may reject 2; CNA does not preflight that native restriction.
SDL Renderer Initial positive values collapse to 1; runtime attempts the integer, then falls back to 1. Tests prove safe reset and pixels, not driver timing.
Canvas, free-direct, Software, Headless No display-interval implementation at this layer. Stored public state may still change.

No located timing test distinguishes half refresh from ordinary VSync on any family.

19.2.5 Backbuffer format, depth format, and fullscreen

Reset first asks the renderer to normalize the requested color and depth formats, then stages the result as public state. Window resizing and virtual-resolution application form a rollback block: if either throws, CNA restores the prior presentation, adapter, touch dimensions, and window size before rethrowing. MSAA, interval, and the renderer’s presentation-format update occur afterward; they are not covered by that rollback. Only D3D9 overrides the final format-update hook. A family whose applied-format query inherits the identity default can therefore echo a request even when its native attachments use a fixed format.

Group Native attachments Reset/fullscreen boundary
D3D9 Maps requested color and exact None/D16/D24/D24S8 depth forms. Performs a device reset; unsupported combinations can fail after public state changed.
D3D11/12 Fixed RGBA8 and D24S8 when a windowed backbuffer exists. SDL changes the window; DXGI exclusive fullscreen is not requested.
Vulkan / WebGPU / SDL GPU / BGFX Selects surface/device formats independently of the XNA enums; depth is fixed or device-selected. Resize/surface recreation follows the window, not the stored enum fidelity.
EasyGL SDL/GL chooses the default framebuffer; its MSAA FBO is fixed RGBA8/depth24 without stencil. Tests establish stored values and selected depth behavior, not format reconfiguration.
SDL Renderer / Canvas / free-direct 2D color target with no default depth/stencil attachment. Fullscreen is an SDL/window or browser concern.
Software / Headless Fixed CPU color/depth arrays, or trace state without attachments. Fullscreen is stored state only.

An SDL fullscreen failure is cleared and ignored, so IsFullScreen is a request, not an actual-state query. D3D9 has the strongest located native format evidence; most other tests prove stored-field round trips or continued rendering.

19.2.6 Backbuffer readback

The interface promises top-left RGBA8 data and throws by default. The public wrapper checks a null destination, positive in-bounds rectangles against the stored backbuffer dimensions, and elementCount < width*height. It does not reject a negative start index, account for remaining capacity after that offset, or guard the signed area multiplication against overflow.

Renderer behavior falls into four observable classes:

Class Families Image returned
Active target SDL Renderer, EasyGL, Software, free-direct, Canvas; historical ASCII Bound target when present, otherwise the family backbuffer/logical surface. Canvas browser execution was not observed for this edition.
Physical/default backbuffer Vulkan, BGFX, D3D9, D3D11, WebGPU Ignores an active user target. Logical and physical dimensions can diverge on scaled/high-density output.
Synthetic Headless Fills with the last global clear color; draw output and source coordinates are not represented.
Explicit refusal SDL GPU, D3D12 Inherited throw, even though both have separate render-target readback mechanisms.

There is no capability query for this distinction. A portable test must name the selected family, source image, coordinate space, and oracle.

19.3 Resource and draw defaults

19.3.1 Target, effect, and query interfaces

IRenderTargetRenderer::GetMultiSampleCount() defaults to zero, while HasRealDepthBuffer(requested) defaults to echoing the request. The first is conservative; the second can claim an attachment that the family never created. SDL Renderer overrides the depth answer to false. Applied sample count and actual attachment presence are therefore stronger facts than constructor arguments.

IOcclusionQueryRenderer itself is pure virtual, but its factory is optional. IEffectRenderer requires program compile/bind/status methods while many scalar, array, and texture setters have no-op defaults. A valid program can consequently discard selected parameters. Chapter 17 owns the renderer-specific matrix.

19.3.2 Buffer updates and instancing

Dynamic buffer constructors currently use the same factory as static buffers; the constructor’s dynamic flag is ignored. Update options still have meaning on EasyGL, SDL GPU, D3D9, and D3D11, which map discard/no-overwrite to distinct native operations. Vulkan and BGFX inherit the option-discarding default; WebGPU, D3D12, Software, and Headless override it without preserving option identity. The options-taking public wrappers also fail to refresh the shared CPU shadow, so a later public GetData() can throw or return stale bytes.

The declaration route is stricter: SetVertexDeclaration() is pure virtual, and raw uploads send the complete declaration. A family may translate it, remember it for draw-time validation, or reject it. Chapter 18 defines the packed-stream contract.

Effect-aware DrawPrimitivesEx defaults can discard GpuDrawParams and enter a colored draw. The exhaustive family audit found DIRECTX10 as the consequential live inherited fallback: an effect request can become a plausible untextured colored result. Several families override the method but still use a colored fallback for unmatched layouts. Appendix B classifies inherited, hybrid, and renderer-owned routes.

Instancing has a throwing default. Shared code now transports up to sixteen bindings, preserving slot, renderer buffer, stride, vertex offset, instance frequency, and count. It rejects partially overlapping semantic declarations and validates per-vertex and per-instance ranges before submission. The broad portable subset is one vertex stream plus one instance stream; multiple streams of either rate additionally require MultiStreamVertexInput. EasyGL profiles with native support, Magnum, Vulkan, BGFX, WebGPU, and D3D11/12 have focused pixel evidence for specific layouts. D3D9 has a native step-frequency route but weaker shared-oracle coverage. Headless records counts, and SDL GPU inherits a default-true capability despite the throwing callback; neither establishes working instancing.

19.3.3 Texture updates and readback

Every current 2D texture implementation overrides level-zero updates. The higher-mip default is still reachable on SDL GPU and silently discards the upload; Software explicitly discards higher levels, while SDL Renderer, Canvas, and free-direct throw. EasyGL, Vulkan, BGFX, WebGPU, and D3D9/11/12 implement higher-level storage. GPU-sampled or native-subresource tests, not public CPU-shadow round trips, establish those paths. WebGPU additionally regenerates the mip chain after level-zero writes, which can overwrite previously authored higher levels.

Volume and cube readback now returns a completion Boolean. Public wrappers convert results only after a true response; null/false raises NotSupportedException without modifying the destination. EasyGL, Vulkan, BGFX (when transfer capabilities exist), WebGPU, SDL GPU, D3D9/11/12, and LLGL have native transfer routes. Skia and selected Software cube paths use exact CPU storage. Headless refuses instead of manufacturing transparent pixels. The shared fixtures distinguish slices, faces, subrectangles, mips, and unchanged sentinel buffers.

RenderTargetCube inherits public texture upload methods, but most renderer-target objects explicitly refuse them; EasyGL and Skia are the notable storage implementations. Target transitions are a separate concern. Families with deferred work must finalize the outgoing face before another cube face, 2D target, MRT set, or backbuffer becomes active. D3D9/11/12, BGFX, WebGPU, SDL GPU, LLGL, and Vulkan have renderer-owned transition mechanisms. EasyGL retains per-face MSAA storage, but switching faces on the same cube can skip outgoing finalization; callers that need freshly resolved/sampleable faces should unbind between them.

19.4 Targets, clears, viewport, and scissor

19.4.1 Render-target identity and usage

Singular 2D and cube calls normalize into one descriptor list. Each descriptor names a 2D target or cube face, dimensions, and applied sample count. The first descriptor controls viewport, scissor, discard clearing, and usage policy. The shared layer rejects null/disposed targets, nonzero 2D slices, dimension or sample-count mismatches, duplicate subresources, and unsupported mixed/cube MRT shapes before native binding.

Three rules are cross-family:

  1. 1.

    CNA has no FNA-style redundant-binding early return. Rebinding the same discard target clears it again and can repeat resolve/finalization work.

  2. 2.

    PreserveContents and PlatformContents both pass the preserve Boolean; exact DiscardContents triggers the shared implicit clear.

  3. 3.

    The discard mask includes color and each depth/stencil aspect the first descriptor reports as present. Backbuffer usage is outside this policy.

Focused fixtures distinguish discard from preserve/platform for major GPU families, including cube and depth/stencil cases. Software has CPU color, depth, and stencil storage; Headless reports trace changes, not preserved pixels. WebGPU and Software explicitly reject more than one target. D3D12 binds multiple RTVs, but current stock draws write target zero. A true MRT capability answer must therefore be paired with a successful multi-target bind and an oracle for the intended outputs.

19.4.2 Clear selection and ordering

GraphicsDevice converts color once, masks unavailable target aspects, and dispatches one of seven non-empty target/depth/stencil combinations. The significant remaining differences are concise:

  • Vulkan, BGFX, WebGPU, and SDL GPU preserve ordered clears across deferred pass segments; focused pixel suites cover masks, chronology, and several target kinds.

  • D3D11/12 issue native immediate clears. D3D9 further gates depth/stencil bits using its backbuffer format, even when a differently formatted target is active.

  • EasyGL’s color-only hook also includes the GL depth bit without forcing depth writes, so prior depth-mask state can change a nominal target-only clear.

  • SDL Renderer, Canvas, and free-direct mask depth/stencil away on their public 2D paths; their direct 3D hooks may throw but are not reached by the masked public call.

  • Software handles color, depth, and stencil in CPU arrays; Headless records trace identity without attachment semantics.

There is no capability value for aspect selectivity or same-pass ordering. Tests must observe the requested aspect after intervening work; an unchanged color pixel alone does not prove that a depth-only clear occurred.

19.4.3 Viewport and scissor

The device calls each renderer hook before committing the corresponding public property, and scissor enable arrives separately through RasterizerState. A successful getter round trip therefore establishes accepted bookkeeping, not rasterization.

Vulkan, BGFX, WebGPU, and SDL GPU capture viewport/scissor per deferred command and have focused pixel evidence. EasyGL and D3D9/11 apply both through native state; EasyGL SpriteBatch resets the viewport to the full destination and does not restore it. D3D12 applies custom viewport and depth range but installs a full-target scissor on every draw. SDL Renderer applies a nonempty clip rectangle regardless of ScissorTestEnable and has no viewport hook. Software applies the rectangle and enable state to its 2D and 3D clip paths; its custom viewport is covered for SpriteBatch, while the 3D NDC mapping still spans the full framebuffer. Canvas, free-direct, and historical ASCII do not implement both semantics. Headless validates/traces but does not rasterize.

Target switches reset viewport and scissor to destination bounds. Construction establishes both full rectangles. Present and a same-size reset preserve custom values; a logical-size or physical default-viewport change resets both. No capability value reports either feature.

19.5 Capability answers and operational evidence

GraphicsCapability contains ThreeD, depth/stencil, MSAA, MRT, anisotropy, wireframe, occlusion query, custom effects, 3D textures, multi-stream input, instancing, stencil, and additive blending. The shared default returns true except for multi-stream input; stencil uses a separate hook. This optimistic polarity makes an answer useful only when the family has audited the corresponding operation.

Figure 18.1 shows the required progression from reported Boolean to an observed result.

Confirmed contradictions and scope mismatches at the pin include:

Capability Affected families Reachable behavior
MRT Headless, Software, WebGPU Trace-only behavior or explicit rejection, not a usable multi-attachment draw contract.
Occlusion query WebGPU, SDL GPU, Software; Headless is synthetic Null factory or non-GPU bookkeeping despite true reporting.
Custom effects BGFX, WebGPU Always-invalid object, null factory, or a later throwing route.
Instancing Headless, SDL GPU Trace-only result or inherited throwing callback.
MSAA Software; several device-dependent families are overbroad Software implements 4x color MSAA for render targets but not its backbuffer; Vulkan/WebGPU/SDL GPU/D3D9 can negotiate a request down while still reporting true.
Anisotropy SDL GPU, Software; D3D9 is overbroad Stored/ignored value, discarded sampler state, or device limit not consulted by the query.

For MSAA, the applied value written back to presentation parameters or the target’s own MultiSampleCount is stronger than the Boolean. For all capabilities, the defensible sequence is query, factory, operation, engagement, then a suitable behavioral or pixel oracle.

19.5.1 Recovery, device-loss, and markers

SetContextRecoveryEnabled() changes shared texture-shadow policy before invoking the renderer hook. Disabling it permits a successful full upload to discard CPU pixels, so later readback or a partial update can fail even when the family hook is empty. Re-enabling recovery does not reconstruct discarded data. Call it after device construction but before loading resources; the header’s “before device initialization” wording cannot be literal because the method belongs to an existing device.

F9 and F10 key-down events pass through input first, then call renderer loss/restore hooks. EasyGL uses them for desktop or browser context-loss routes. D3D9 exposes a staged lost/reset lifecycle with event and pixel coverage. Other audited families generally inherit no-ops. SetStringMarkerEXT() is also a no-op by default; Vulkan can queue a debug-utils marker when the extension function exists, while D3D9 throws. Successful submission is not proof that a native marker was emitted.

19.6 Selection and factory boundary

Chapter 20 is the canonical identity/family registry. At this edition, one configure-time public identity maps to one of 46 implementation families; the five EasyGL identities share one family. Platform and dependency gates run before the factory is compiled.

The selected module provides exactly one CreateGraphicsRenderer(const GraphicsRendererCreateArgs&) returning unique_ptr<IGraphicsRenderer>. Its create arguments include window, virtual resolution, presentation mode, recovery policy, requested samples and interval, plus CNAEXT backbuffer/depth formats, fullscreen, and profile. Field presence is not a requirement that every family consume the field; the presentation tables above show the current differences.

Historical note.  The original audit narrated individual task numbers, intermediate tests, and repaired defaults throughout this chapter. The current contract now incorporates those fixes. History remains here only where it explains a live hazard: permissive defaults allowed plausible reduced draws; CPU-shadow round trips masked missing GPU uploads; and direct helper tests bypassed the public coordinate route. The detailed dated campaign remains in the repository audit artifacts.

19.7 Implications for users and implementers

For application code:

  • choose a renderer by the operations and evidence your program needs, not by selector name;

  • treat capability answers as preflight hints and verify applied values or results;

  • keep resources address-stable and within device lifetime;

  • distinguish logical presentation coordinates from physical readback pixels;

  • expect custom effects, MRT, queries, instancing, and readback to require family-specific qualification.

For a renderer implementation, overriding a virtual method is only the beginning. A complete claim for one operation needs a reachable factory, shared public routing, applied state, correct target and lifetime behavior, renderer-engagement evidence, and a discriminating oracle. That is the standard used by the family chapters that follow.

Type at least three characters. Results are ranked by how often and where the words occur.