@solidrt/2d 0.0.53 → 0.0.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -224,7 +224,8 @@ is flat. Event x/y are layer pixels with the camera undone.
224
224
  (nearest inside keeps blocks square), the linear composite spreads the
225
225
  fraction over one device pixel at block edges. The components pick n
226
226
  every layout from their leaf's window box (`getBoundingBoxViewport` x
227
- `displayScale()`, which composes designSize fits and camera zoom); the
227
+ `displayScale()`, which composes designSize fits and camera zoom), within
228
+ a budget of the window's own device pixel count; the
228
229
  primitives default to 1 and take `{ oversample }` / `setOversample(n)`
229
230
  - with `output` on `<SpriteLayer>` there is no built-in leaf, so set it
230
231
  yourself. Never fix a shimmer by snapping the fit to an integer: the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/2d",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -16,6 +16,6 @@
16
16
  ],
17
17
  "peerDependencies": {
18
18
  "@solidjs/signals": "2.0.0-rc.3",
19
- "@solidrt/core": "0.0.53"
19
+ "@solidrt/core": "0.0.54"
20
20
  }
21
21
  }
@@ -6,13 +6,21 @@
6
6
  // sprite with `ref` and call setSprite from onFrame - signals carry
7
7
  // structure and slow state, per-frame motion goes straight to the layer.
8
8
  // The same split, with the same reasoning, as @solidrt/3d's components.
9
- import { createContext, createEffect, createSignal, displayScale, For, getBoundingBoxViewport, onCleanup, onLayout, untrack, useContext } from "@solidrt/core"
9
+ import { createContext, createEffect, createSignal, displayScale, For, getBoundingBoxViewport, onCleanup, onLayout, untrack, useContext, windowSize } from "@solidrt/core"
10
10
  import type { Element, ParentComponent, TextureId, VoidComponent } from "@solidrt/core"
11
11
  import type { FilterMode } from "@solidrt/core/gpu"
12
12
  import { addGroup, addSprite, createSpriteLayer, removeGroup, removeSprite, setGroup, setGroupTransition, setSprite, setSpriteTransition } from "./layer.ts"
13
13
  import type { NodeTransition } from "flux:spatial"
14
14
  import type { CameraUpdate, Sprite as SpriteHandle, SpriteGroup, SpriteLayer as LayerHandle, SpriteOptions, SpritePointerEvent, TransitionEndEvent } from "./layer.ts"
15
15
  import { fitOversample } from "./oversample.ts"
16
+
17
+ // The window's device pixel count: the texel budget an auto-picked
18
+ // oversample target stays within (see fitOversample).
19
+ function windowTexels(): number {
20
+ let win = windowSize()
21
+ let scale = displayScale()
22
+ return win.width * scale * (win.height * scale)
23
+ }
16
24
  import { createTileLayer } from "./tiles.ts"
17
25
  import type { TileChunk, TileLayer as TileLayerHandle } from "./tiles.ts"
18
26
 
@@ -117,7 +125,7 @@ export let SpriteLayer: ParentComponent<SpriteLayerProps> = props => {
117
125
  let box = getBoundingBoxViewport(leaf)
118
126
  if (!box) return
119
127
  let scale = displayScale() * Math.max(box.width / props.width, box.height / props.height)
120
- layer.setOversample(fitOversample(scale, props.width, props.height))
128
+ layer.setOversample(fitOversample(scale, props.width, props.height, windowTexels()))
121
129
  }
122
130
  onLayout(pick)
123
131
  createEffect(() => displayScale(), pick)
@@ -316,7 +324,7 @@ export let TileLayer: VoidComponent<TileLayerProps> = props => {
316
324
  let box = getBoundingBoxViewport(world)
317
325
  if (!box) return
318
326
  let scale = displayScale() * Math.max(box.width / layer.width, box.height / layer.height)
319
- layer.setOversample(fitOversample(scale, layer.chunkW, layer.chunkH))
327
+ layer.setOversample(fitOversample(scale, layer.chunkW, layer.chunkH, windowTexels()))
320
328
  }
321
329
  onLayout(pick)
322
330
  createEffect(() => displayScale(), pick)
package/src/oversample.ts CHANGED
@@ -6,11 +6,6 @@
6
6
  // instead of snapping to uneven widths). okf/backlog/2d-layer-display-scale.md.
7
7
  import { limits } from "@solidrt/core/gpu"
8
8
 
9
- /** Ceiling on an auto-picked oversample: a small layer in a huge box would
10
- * otherwise ask for a target the size of the window squared. Eight covers a
11
- * 4x design fit on a 2x display. */
12
- export const MAX_OVERSAMPLE = 8
13
-
14
9
  /** Tolerance under a whole number when rounding a display scale up, so a
15
10
  * scale that is 3 up to float noise picks 3, not 4. */
16
11
  const FIT_EPSILON = 1e-6
@@ -28,12 +23,18 @@ export function checkOversample(verb: string, n: number, width: number, height:
28
23
 
29
24
  /**
30
25
  * The oversample for a layer shown at `scale` device pixels per layer pixel:
31
- * the ceiling of the scale, so no device pixel goes without a target texel,
32
- * capped by MAX_OVERSAMPLE and by what a `targetW x targetH` target can grow
33
- * to on this device.
26
+ * the ceiling of the scale, so no device pixel goes without a target texel.
27
+ * Bounded by `budget` texels for the `targetW x targetH` target (the
28
+ * window's own device pixel count: a target beyond it buys nothing on
29
+ * screen, and a layer stretched far past the window inside a scroller must
30
+ * not ask for one) and by what the target can grow to on this device.
34
31
  */
35
- export function fitOversample(scale: number, targetW: number, targetH: number): number {
36
- let n = Math.max(1, Math.ceil(scale - FIT_EPSILON))
32
+ export function fitOversample(scale: number, targetW: number, targetH: number, budget: number): number {
33
+ // The budget bounds the scale, not the rounded factor: the ceiling may
34
+ // still round a fit that fills the window up by one (a 320 x 200 design
35
+ // on a 2560 x 1440 panel fits at 7.2 and needs 8).
36
+ let byBudget = Math.sqrt(budget / (targetW * targetH))
37
+ let n = Math.max(1, Math.ceil(Math.min(scale, byBudget) - FIT_EPSILON))
37
38
  let byDevice = Math.floor(limits.maxTextureSize / Math.max(targetW, targetH))
38
- return Math.max(1, Math.min(n, MAX_OVERSAMPLE, byDevice))
39
+ return Math.max(1, Math.min(n, byDevice))
39
40
  }