RealDocs

UMG/Slate Coordinate Spaces

UMG and Slate expose five distinct coordinate spaces, and the conversion API is scattered across FGeometry, USlateBlueprintLibrary, and UWidgetLayoutLibrary with no single reference. To make matters worse, "Absolute" does not mean screen-relative, DPI scaling silently multiplies coordinates, and the two outputs of LocalToViewport are trivially easy to mix up. This page maps every space from the outermost (OS desktop) to the innermost (individual widget), explains how they relate, and lists every conversion function in one place.

1. The Five Spaces

Each space has a different origin, unit size, and purpose. They nest from the outermost (the OS desktop) down to each individual widget. Sections 2–6 cover each space in detail.

Outermost
Screen / Desktop Space
✏️ Raw OS pixel coordinates. Origin = top-left of the primary monitor. Can be negative on multi-monitor setups. Unaffected by DPI scaling.
Absolute / Window Space
✏️ Origin = top-left of the game window. In fullscreen this equals screen space. In windowed mode it is offset by the window's desktop position. In PIE the "window" is the entire editor window, not just the game viewport panel.
Viewport Space — Pixel
✏️ Raw device pixels, measured from the top-left of the game viewport widget. The viewport does not always fill the window — in PIE it is a panel inside the editor, surrounded by editor chrome.
Viewport Space — Scaled (Logical)
✏️ The same viewport origin, but in DPI-independent units: Pixel Position ÷ DPI scale.
This is the space that UMG widget placement (SetPositionInViewport), HUD drawing, and most gameplay math expect. A value of (960, 540) always means "the centre of a 1920×1080-logical viewport", regardless of the physical pixel count on the display.
Local Space
✏️ Per-widget coordinates in Slate units. Origin = the widget's own top-left corner. Values outside the widget's Size (negative or > Size) are valid — they just describe points beyond the widget's edges.
Paint / Render Space
✏️ Local space warped by render transforms (rotation, scale, skew). Affects the widget's visual appearance but NOT its layout bounds or hit-test area.
📝 Absolute and Screen space only coincide when the window sits at desktop position (0, 0). This is why UI code can work fine in fullscreen on the primary monitor, but break when players run the game in windowed mode, or on a non-primary monitor.

2. Screen / Desktop Space

Screen space is the raw OS coordinate system. Its origin is the top-left corner of the primary monitor, and it spans the entire virtual desktop (all monitors combined). Units are device pixels — no DPI scaling applied. UE exposes it mainly for mouse position queries and for ingesting coordinates that come from a platform SDK or external tool.

Mouse position functions

Function Returns Notes
GetMousePositionOnPlatform() Desktop device pixels No world context. Can be negative on multi-monitor setups.
GetMousePositionOnViewport(World) Viewport scaled units Preferred for UMG work — accounts for window position and DPI automatically.
GetMousePositionScaledByDPI(PC, X, Y) DPI-scaled cursor pos Requires a PlayerController. Outputs separate float X/Y.

Converting screen → widget

FVector2D ScreenPos = /* coord from platform SDK or OS input event */;
FVector2D LocalCoord;

// bIncludeWindowPosition: pass true in windowed mode, false in fullscreen
USlateBlueprintLibrary::ScreenToWidgetLocal(
    GetWorld(),
    MyWidget->GetCachedGeometry(),
    ScreenPos,
    LocalCoord,
    true   // <-- set correctly for windowed vs fullscreen!
);
Pass true in windowed mode so the window's desktop offset is subtracted before the conversion. Pass false in fullscreen (the offset is zero, so it's a no-op, but true is safe there too). Getting this wrong in windowed mode shifts every coordinate by the window border — often 50–200 pixels.
GetMousePositionOnPlatform() returns coordinates in the OS virtual desktop, which spans all connected monitors. If a secondary monitor is positioned to the left or above the primary, X or Y can be negative. Always clamp or offset before passing into UMG.
📝 In Play In Editor the editor window is not fullscreen — the game viewport is a panel embedded in it. GetMousePositionOnPlatform() still returns the correct desktop cursor position, but that position will be somewhere inside the editor window, not at the game viewport's origin. Use GetMousePositionOnViewport() instead for game-relative mouse math in PIE.

3. Absolute / Window Space

"Absolute" in the Slate/UMG API means relative to the Slate root window's top-left corner — not the desktop, and not the game viewport. It is computed by accumulating layout transforms from the root widget down to the widget in question (FGeometry::AccumulatedLayoutTransform). Render transforms (rotation, scale, skew) are not included.

In a standalone game the Slate root window is the game window, so Absolute ≈ Window. In PIE the Slate root window is the entire editor window — absolute coordinates are therefore measured from the editor window's corner, not the game viewport panel's corner.

Key functions

Function Direction Available in
FGeometry::LocalToAbsolute Local → Absolute C++
FGeometry::AbsoluteToLocal Absolute → Local C++
USlateBlueprintLibrary::LocalToAbsolute Local → Absolute C++, Blueprint
USlateBlueprintLibrary::AbsoluteToLocal Absolute → Local C++, Blueprint
The game window can be positioned anywhere on the desktop. In fullscreen (borderless or exclusive) they coincide because the window origin is (0, 0). The comment in Geometry.h acknowledges this ambiguity: "Absolute coordinates could be either desktop or window space depending on what space the root of the widget hierarchy is in." In practice, Slate's root is always the OS window, so Absolute = Window.
In PIE the Slate root window is the editor window. Absolute coordinates include the offset from the editor window's top-left to wherever the game viewport panel sits inside it (the tab bar, toolbar, and content browser all contribute to this offset). Raw absolute values will not match what you'd see in a standalone build. Always go through GetViewportWidgetGeometry() when you need viewport-relative positions — it correctly handles both PIE and standalone.

4. Viewport Space (Pixel vs Scaled)

The game viewport is an SViewport Slate widget. Its top-left corner is the origin of viewport space. Crucially, the viewport does not have to fill the window:

  • PIE (Play In Editor): the viewport is a panel embedded inside the editor window. The editor chrome — tabs, toolbar, content browser — surrounds it. The viewport's pixel origin is wherever that panel starts inside the editor window.
  • Sub-viewport configurations: a game can render to a viewport that is smaller than the window (e.g. a picture-in-picture inset, or a split-screen sub-rect configured via UGameViewportClient).
  • Standalone game: in the typical case the viewport fills the window and viewport pixel space equals absolute/window space.

Viewport space itself comes in two flavours that are always returned as a pair by conversion functions:

Output Units DPI scaled? Use when
Pixel Position Raw device pixels No Render targets, scissor rects, hardware cursor APIs
Viewport Position Slate units (see §5) Yes — Pixel ÷ DPI scale Placing UMG widgets, HUD canvas drawing, SetPositionInViewport
FVector2D PixelPos, ViewportPos;
USlateBlueprintLibrary::LocalToViewport(
    GetWorld(), MyWidget->GetCachedGeometry(),
    FVector2D(0.f, 0.f),  // local point (widget origin)
    PixelPos,             // OUT: raw device pixels
    ViewportPos           // OUT: Slate units (DPI-scaled)
);

// Relationship between the two:
float DPIScale = UWidgetLayoutLibrary::GetViewportScale(GetWorld());
// ViewportPos == PixelPos / DPIScale  (approximately)

// Viewport logical size (Slate units):
FVector2D LogicalSize = UWidgetLayoutLibrary::GetViewportSize(GetWorld());
// Physical pixel size = LogicalSize * DPIScale
Pixel Position and Viewport Position are identical at 1× DPI (standard 1080p monitor). On a 2× HiDPI display or a console with non-default DPI scale they differ by a factor of 2+. Always use Viewport Position for UMG widget placement. Passing Pixel Position to SetPositionInViewport will look correct on your dev machine then place widgets in the wrong position on a Retina display or high-DPI monitor.
📝 The game viewport in PIE is a sub-panel of the editor window. GetViewportSize() and GetViewportScale() report the size and DPI of that sub-panel, not the editor window as a whole — so viewport-space math is PIE-safe as long as you go through these functions rather than hardcoding sizes.

5. Local Space

Local space is the coordinate system internal to a single widget. The origin (0, 0) is the widget's own top-left corner. The X axis grows right and Y grows down. The "size" of the space is the widget's FGeometry::Size — a local point at (Size.X, Size.Y) is exactly the bottom-right corner.

Points outside those bounds are perfectly valid values in local space — they just describe positions beyond the widget's edges. A point at (-10, 5) is 10 units to the left of the widget's left edge. You'll encounter these when you call AbsoluteToLocal on an absolute coordinate that isn't inside the widget, or when doing drag-offset math.

What are Slate units?

Slate units are resolution-independent layout units — the same concept as CSS pixels, iOS "points", or Android "dp/dip". One Slate unit always represents the same physical size on screen regardless of monitor resolution or pixel density. The DPI scale factor (FGeometry::Scale) bridges them to actual device pixels:

device_pixels = slate_units × FGeometry::Scale

// Examples:
//   1× DPI (standard 1080p):  100 Slate units = 100 device pixels
//   2× DPI (Retina / 4K HiDPI): 100 Slate units = 200 device pixels
//   1.5× DPI (some 1440p setups): 100 Slate units = 150 device pixels

So Slate units are not always 1:1 with device pixels — only at 1× DPI. However they are always 1:1 with Viewport Position (the scaled viewport coordinate from §4), because both use the same DPI-independent unit. The practical benefit: if you hardcode a widget position in Slate units (e.g. SetPositionInViewport(FVector2D(960, 540))), it will visually land at the same screen position on every DPI setting — the engine multiplies by the DPI scale to get the final pixel placement.

Getting a widget's geometry

// C++ — call from NativeTick, NOT from NativeConstruct
const FGeometry& Geom = MyWidget->GetCachedGeometry();

// The widget's size in Slate units
FVector2D WidgetSize = FVector2D(Geom.Size);

// Centre of the widget in local space
FVector2D Centre = WidgetSize * 0.5f;

// Blueprint — "Get Cached Geometry" node on any Widget reference

Converting local ↔ absolute

FVector2D LocalPt    = FVector2D(50.f, 20.f);
FVector2D AbsPoint   = Geom.LocalToAbsolute(LocalPt);    // local → window
FVector2D BackLocal  = Geom.AbsoluteToLocal(AbsPoint);   // window → local

// A point outside the widget — valid, just off the edge:
FVector2D OutsidePt  = FVector2D(-5.f, Geom.Size.Y + 10.f);  // above-left + below-right
FVector2D AbsOutside = Geom.LocalToAbsolute(OutsidePt);       // still works fine
The cached geometry is populated after the widget's first Tick/Paint pass. Calling it in NativeConstruct, immediately after AddToViewport, or in any constructor context returns a zeroed FGeometrySize = (0, 0), AbsolutePosition = (0, 0). Defer geometry reads to NativeTick, or guard with if (Geom.Size.X > 0.f).
📝 Local space is entirely internal to the widget and has no dependence on the window or viewport. It behaves identically in PIE and standalone. The only difference is that FGeometry::Scale may differ between PIE (editor DPI setting) and standalone (game DPI setting) if they are configured differently.

6. Paint / Render Space

When you apply a render transform to a widget via SetRenderTransform, SetRenderTransformAngle, or SetRenderScale, you introduce a paint space that diverges from local/layout space. The widget looks rotated or scaled on screen, but the layout engine and hit-test system still use the original unmodified geometry. Render transforms are purely a rendering effect.

Method Includes render transforms? Use for
GetCachedGeometry() No — layout only Coordinate conversions, hit tests, most UMG work
GetTickSpaceGeometry() No — layout only Preferred in NativeTick (equivalent to GetCachedGeometry)
GetPaintSpaceGeometry() Yes Finding the visual bounding box of a transformed widget
GetLayoutBoundingRect() No AABB in absolute space — always axis-aligned
GetRenderBoundingRect() Yes AABB enclosing the rendered widget (may be larger due to rotation)
AbsoluteToLocal and LocalToAbsolute work in layout space only. If you've rotated a widget 45° with SetRenderTransformAngle, clicking on its visual centre will not give you (Width/2, Height/2) in local space — the layout geometry is still axis-aligned at the original unrotated position. Hit tests use layout geometry too, so the clickable area and the visible area will not match.

7. FGeometry Reference

FGeometry is the core struct that encodes a widget's position, size, and accumulated transforms. You retrieve it from GetCachedGeometry() and pass it into conversion functions.

Member / Method Type Space What it gives you
AbsolutePosition FVector2f Absolute Widget's top-left corner in absolute (window) space, in device pixels
Size FVector2f Local (Slate units) Widget's width and height in Slate units
Scale float DPI scale factor: device_pixels = slate_units × Scale
LocalToAbsolute(local) FVector2D Local → Absolute Converts a local Slate-unit point to absolute (window) space
AbsoluteToLocal(abs) FVector2D Absolute → Local Converts an absolute point back to local space (can be negative / outside bounds)
GetAbsolutePositionAtCoordinates(norm) FVector2D Absolute Normalised UV → absolute: (0,0)=TL, (0.5,0.5)=centre, (1,1)=BR
GetAbsoluteSize() FVector2D Absolute (device px) Widget size in device pixels: Size × Scale
GetLayoutBoundingRect() FSlateRect Absolute AABB in absolute space — layout only, no render transforms
GetRenderBoundingRect() FSlateRect Absolute (paint) AABB in absolute space — includes render transforms (larger if rotated)
IsUnderLocation(abs) bool Absolute Hit test: is an absolute-space point inside this widget's layout bounds?
📝 You can copy it out of GetCachedGeometry() and hold it across frames, but the copy becomes stale the next time the widget is laid out (position or size changes). For single-frame work this is safe; for multi-frame caching, re-fetch each tick.

8. Multiple Viewports & AddToPlayerScreen

Widgets can be added to the full game viewport or to a per-player screen area. In split-screen these are different regions of the window; in single-player they cover the same area but have different geometry origins.

Function Scope Notes
AddToViewport(ZOrder) Full game viewport Shared across all local players. ZOrder controls draw order.
AddToPlayerScreen(ZOrder) Per-player area Correct for split-screen HUD. Requires a valid owning player.
GetViewportWidgetGeometry(World) Full viewport geometry Pair with widgets added via AddToViewport.
GetPlayerScreenWidgetGeometry(PC) Player screen geometry Pair with widgets added via AddToPlayerScreen.
In single-player the viewport and player screen have the same size, but their geometry origins differ — the player screen origin is offset by the player's viewport sub-rect. Using GetViewportWidgetGeometry to convert coordinates for a widget added with AddToPlayerScreen will be slightly wrong in single-player and completely wrong in split-screen.
📝 SetPositionInViewport and SetDesiredSizeInViewport take DPI-scaled Slate units, not device pixels. The optional bRemoveDPIScale parameter (default true) divides the value by the DPI scale before applying — pass false only if you have already done the division yourself.

9. Converting Between Spaces — C++

LocalToViewport — annotated example

// Call from NativeTick or a mouse-event handler
const FGeometry& Geom = MyWidget->GetCachedGeometry();
FVector2D LocalPt = FVector2D(Geom.Size) * 0.5f;  // widget centre in Slate units

FVector2D PixelPos;     // raw device pixels — use for render targets
FVector2D ViewportPos;  // DPI-scaled Slate units — use for SetPositionInViewport

USlateBlueprintLibrary::LocalToViewport(
    GetWorld(), Geom, LocalPt,
    PixelPos,    // OUT 1
    ViewportPos  // OUT 2
);

// Never pass PixelPos to SetPositionInViewport — wrong on HiDPI
MyOtherWidget->SetPositionInViewport(ViewportPos);

Scalar / vector helpers

When converting a distance or size (not a position) between spaces, use the scalar/vector variants — they avoid introducing any translation offset:

// Convert a 20-unit absolute offset to local-space units
float LocalOffset = USlateBlueprintLibrary::Scalar_AbsoluteToLocal(Geom, 20.f);

// Convert a local-space drag delta to absolute
FVector2D AbsDelta = USlateBlueprintLibrary::Vector_LocalToAbsolute(Geom, DragDelta);

10. Converting Between Spaces — Blueprint

All conversion functions are available as Blueprint nodes. The graphs below show each conversion pattern in a realistic context.

Local → Viewport (both pixel and scaled)

Use Get Cached Geometry on a widget reference, then feed it into Local to Viewport from the Slate Blueprint Library. The node has two output pins — Pixel Position and Viewport Position. Use Viewport Position for widget placement; Pixel Position for render targets or hardware cursor APIs.

Event Tick → Local to Viewport → Set Position In Viewport (use Viewport Position, not Pixel Position) Blueprint
Event Tick Local to Viewport Slate Blueprint Library World Context Geometry Local Coordinate Pixel Position Viewport Position Set Position In Viewport Target is User Widget Target Position Remove DPI Scale true true Get Cached Geometry Target is Widget Target Return Value Make Vector 2D X 0.0 Y 0.0 Return Value Return Value Follower Widget Follower Widget Follower Widget

Absolute → Viewport

Absolute to Viewport converts a window-space absolute coordinate to both viewport pixel and scaled positions. Useful when receiving an absolute coord from another system and needing it for widget placement.

Event Tick → Absolute to Viewport (from a stored absolute position) → Set Position In Viewport Blueprint
Event Tick Absolute to Viewport Slate Blueprint Library World Context Absolute Desktop Position Absolute Desktop Position Pixel Position Viewport Position Set Position In Viewport Target is User Widget Target Position Remove DPI Scale true true Stored Absolute Pos Stored Absolute Pos Stored Absolute Pos Tooltip Widget Tooltip Widget Tooltip Widget

Screen → Widget Local (with bIncludeWindowPosition)

Screen to Widget Local converts an OS screen coordinate into a specific widget's local space. Set Include Window Position to true in windowed mode to subtract the window's desktop offset.

Event Tick → Get Mouse Position On Platform → Screen to Widget Local (Include Window Position = true) → Set canvas slot position Blueprint
Event Tick Screen to Widget Local Slate Blueprint Library World Context Geometry Screen Position Include Window Position Include Window Position true true Local Coordinate Set Position Target is Canvas Panel Slot Target Position Get Cached Geometry Target is Widget Target Return Value Get Mouse Position On Platform Widget Layout Library Return Value Marker Slot Marker Slot

Mouse position in viewport space

The simplest "where is the mouse in my viewport" pattern — Get Mouse Position on Viewport from Widget Layout Library returns a DPI-scaled Viewport Position directly, no geometry needed.

Event Tick → Get Mouse Position On Viewport → Set Position In Viewport — cursor-following widget Blueprint
Event Tick Get Mouse Position On Viewport Widget Layout Library World Context Return Value Set Position In Viewport Target is User Widget Target Position Remove DPI Scale true true Cursor Widget Cursor Widget Cursor Widget

World 3D → Viewport (worldspace UI / nameplates)

Project World Location to Widget Position converts a 3D world position to a 2D viewport pixel position — the core of any worldspace nameplate or ping system. Enable Player Viewport Relative when using AddToPlayerScreen so the result is relative to the player's sub-rect in split-screen.

Event Tick → Project World Location to Widget Position → Set Position In Viewport — nameplate tracks a 3D actor Blueprint
Event Tick Project World Location to Widget Position Widget Layout Library Player Controller World Location Player Viewport Relative false false Viewport Position Return Value Set Position In Viewport Target is User Widget Target Position Remove DPI Scale true true Get Player Controller Player Index 0 Return Value Target World Location Target World Location Target World Location Nameplate Widget Nameplate Widget Nameplate Widget

11. Common Gotchas

⚡ "Absolute" doesn't mean screen-relative

The UMG/Slate "Absolute" space is window-relative, not desktop-relative. In fullscreen the window origin is (0, 0) so this distinction is invisible — code works fine in fullscreen then breaks in windowed mode when the window is offset from the desktop origin. If you need actual screen (desktop) coordinates, use GetMousePositionOnPlatform or the ScreenToWidget* family with bIncludeWindowPosition = true.

⚡ LocalToViewport returns TWO different things

The node outputs both Pixel Position (device pixels, no DPI scaling) and Viewport Position (DPI-scaled Slate units). They are identical at 1× DPI and differ by a factor of 2+ on a Retina display or HiDPI console. Always use Viewport Position for UMG widget placement and HUD drawing; Pixel Position is for render targets and hardware cursor APIs only.

⚡ GetCachedGeometry() is zero on the first frame

The cached geometry is populated after the widget's first Tick/Paint pass. Calling GetCachedGeometry() in NativeConstruct, immediately after AddToViewport, or in any constructor context returns a zeroed struct — Size = (0, 0), AbsolutePosition = (0, 0). Defer geometry reads to NativeTick, or guard with if (Geom.Size.X > 0.f).

⚡ bIncludeWindowPosition must match windowed vs fullscreen

The bIncludeWindowPosition parameter on ScreenToWidgetLocal and ScreenToWidgetAbsolute controls whether the window's desktop offset is subtracted. In fullscreen the offset is zero so the flag has no visible effect. In windowed mode passing false offsets every coordinate by the window border — often 50–200 pixels. Safest default: always pass true.

⚡ Render transforms break coordinate conversion and hit tests

SetRenderTransformAngle / SetRenderScale only affect how the widget is drawn. Layout and hit-test geometry remain at the original unrotated position. AbsoluteToLocal operates in layout space only — if you rotate a widget 45° then call AbsoluteToLocal on a point visually inside it, the result will be wrong. Use GetPaintSpaceGeometry() or GetRenderBoundingRect() when you need the actual rendered position.

⚡ AddToPlayerScreen and GetViewportWidgetGeometry don't mix

In split-screen the player screen is a sub-region of the full viewport with a non-zero origin offset. Even in single-player the geometry origins differ. If you add a widget with AddToPlayerScreen but convert coordinates using GetViewportWidgetGeometry, positions will be shifted by that offset. Always pair: AddToViewport + GetViewportWidgetGeometry, or AddToPlayerScreen + GetPlayerScreenWidgetGeometry(PC).

⚡ Absolute space in PIE is relative to the editor window, not the game viewport

In PIE the Slate root window is the entire editor window. Raw absolute coordinates therefore include the offset from the editor window corner to wherever the game viewport panel is positioned (tabs, toolbars, content browser all contribute). Code that works with hardcoded absolute values in standalone will produce wrong results in PIE. Always go through GetViewportWidgetGeometry() to anchor viewport-relative math correctly in both environments.