@flare-engine/render 0.4.7 → 0.4.10

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/dist/index.mjs CHANGED
@@ -782,12 +782,21 @@ function flashIntensity(effect) {
782
782
  // src/frame-renderer.ts
783
783
  /** See the dev-flag note at the top of this file. */
784
784
  const G = globalThis;
785
+ /** `SpriteDrawCommand.rotation` is radians; `DrawSurfaceLike.rotate` takes degrees. */
786
+ const RAD_TO_DEG = 180 / Math.PI;
785
787
  const BLEND_SRC_OVER = 3;
786
788
  const BLEND_SRC_IN = 5;
787
789
  /** Skia BlendMode.SrcATop — r = s·da + d·(1−sa): keeps the art's alpha, lerps colour by the filter's alpha. */
788
790
  const BLEND_SRC_ATOP = 9;
789
791
  /** Skia BlendMode.Plus — additive: dst = clamp(src + dst). */
790
792
  const BLEND_PLUS = 12;
793
+ /** Shared empty result for `compilePostFxPasses`, so a chain-free frame allocates nothing. */
794
+ const EMPTY_COMPILED_PASSES = [];
795
+ /** The two sampling objects `setImageSampling` hands to the surface — shared, never rebuilt. */
796
+ const SAMPLING_NEAREST = { filter: 0 };
797
+ const SAMPLING_LINEAR = { filter: 1 };
798
+ /** Skia MipmapMode.None — the stretch path never mipmaps. */
799
+ const MIPMAP_NONE = 0;
791
800
  const OUTLINE_OFFSETS = [
792
801
  [1, 0],
793
802
  [-1, 0],
@@ -846,6 +855,7 @@ function createFrameRenderer(Skia) {
846
855
  const stretchDstRects = [];
847
856
  let stretchPaint = null;
848
857
  const zoneBgRects = [];
858
+ const imageSampling = /* @__PURE__ */ new WeakMap();
849
859
  let _cachedSrcSlice = null;
850
860
  let _cachedSrcSliceLen = 0;
851
861
  let _cachedXformSlice = null;
@@ -893,6 +903,17 @@ function createFrameRenderer(Skia) {
893
903
  _loggedOnce.add(msg);
894
904
  console.warn(`[postfx] ${msg}`);
895
905
  }
906
+ function compilePostFxPasses(runtime, chain, width, height) {
907
+ const rt = runtime ?? _boundRuntime;
908
+ const ch = chain ?? _boundChain;
909
+ if (rt === null || !rt.supported) return EMPTY_COMPILED_PASSES;
910
+ if (rt.buildPass === void 0) {
911
+ logOnce("buildPostFxPasses: runtime.supported but buildPass absent — no passes");
912
+ return EMPTY_COMPILED_PASSES;
913
+ }
914
+ if (ch === null) return EMPTY_COMPILED_PASSES;
915
+ return rt.buildPass(ch, width, height);
916
+ }
896
917
  const canvas = {
897
918
  save: () => current?.save() ?? 0,
898
919
  restore: () => {
@@ -1201,21 +1222,19 @@ function createFrameRenderer(Skia) {
1201
1222
  mountainHillsPaint.setShader(mountainHillsEffect.makeShader(mountainHillsUniforms));
1202
1223
  return mountainHillsPaint;
1203
1224
  }
1204
- function drawOutlined(cvs, image, commands, n, effect, alpha) {
1225
+ function drawOutlined(cvs, image, commands, n, effect, alpha, sampling) {
1205
1226
  const paint = applyOutlinePaint(effect);
1206
1227
  paint.setAlphaf(alpha);
1207
1228
  const srcs = getCachedSrcSlice(n);
1208
1229
  for (let k = 0; k < OUTLINE_OFFSETS.length; k++) {
1209
1230
  const offset = OUTLINE_OFFSETS[k];
1210
1231
  fillXforms(commands, n, offset[0] * effect.width, offset[1] * effect.width);
1211
- const dsts = getCachedXformSlice(n);
1212
- cvs.drawAtlas(image, srcs, dsts, paint);
1232
+ drawAtlasSampled(cvs, image, srcs, getCachedXformSlice(n), paint, sampling);
1213
1233
  }
1214
1234
  paint.setAlphaf(1);
1215
1235
  defaultPaint.setAlphaf(alpha);
1216
1236
  fillXforms(commands, n, 0, 0);
1217
- const mainDsts = getCachedXformSlice(n);
1218
- cvs.drawAtlas(image, srcs, mainDsts, defaultPaint);
1237
+ drawAtlasSampled(cvs, image, srcs, getCachedXformSlice(n), defaultPaint, sampling);
1219
1238
  defaultPaint.setAlphaf(1);
1220
1239
  }
1221
1240
  function resolveEffectPaint(effect) {
@@ -1259,6 +1278,7 @@ function createFrameRenderer(Skia) {
1259
1278
  const blendAdditive = cmd0.blend === "additive";
1260
1279
  const flipX = cmd0.flipX;
1261
1280
  const flipY = cmd0.flipY;
1281
+ const sampling = imageSampling.get(image);
1262
1282
  currentSpriteW = cmd0.dstW;
1263
1283
  currentSpriteH = cmd0.dstH;
1264
1284
  if (effect?.type === "roofs-city" || effect?.type === "forest-canopy" || effect?.type === "river-wave" || effect?.type === "moon-crater" || effect?.type === "cave-interior" || effect?.type === "mountain-hills") {
@@ -1292,12 +1312,13 @@ function createFrameRenderer(Skia) {
1292
1312
  const cx = cmd.dstX + cmd.dstW * .5;
1293
1313
  const cy = cmd.dstY + cmd.dstH * .5;
1294
1314
  current.save();
1315
+ if (cmd.rotation !== 0) current.rotate(cmd.rotation * RAD_TO_DEG, cmd.dstX + cmd.anchorX * cmd.dstW, cmd.dstY + cmd.anchorY * cmd.dstH);
1295
1316
  current.translate(cx, cy);
1296
1317
  current.scale(cmd.flipX ? -1 : 1, cmd.flipY ? -1 : 1);
1297
1318
  current.translate(-cx, -cy);
1298
- current.drawImageRect(image, srcR, dstR, sPaint);
1319
+ drawImageRectSampled(current, image, srcR, dstR, sPaint, sampling);
1299
1320
  current.restore();
1300
- } else current.drawImageRect(image, srcR, dstR, sPaint);
1321
+ } else drawImageRectSampled(current, image, srcR, dstR, sPaint, sampling);
1301
1322
  }
1302
1323
  sPaint.setAlphaf(1);
1303
1324
  if (blendAdditive) sPaint.setBlendMode?.(BLEND_SRC_OVER);
@@ -1310,7 +1331,7 @@ function createFrameRenderer(Skia) {
1310
1331
  outlinePaint?.setBlendMode?.(BLEND_PLUS);
1311
1332
  defaultPaint.setBlendMode?.(BLEND_PLUS);
1312
1333
  }
1313
- drawOutlined(current, image, commands, n, effect, alpha);
1334
+ drawOutlined(current, image, commands, n, effect, alpha, sampling);
1314
1335
  if (blendAdditive) {
1315
1336
  outlinePaint?.setBlendMode?.(BLEND_SRC_OVER);
1316
1337
  defaultPaint.setBlendMode?.(BLEND_SRC_OVER);
@@ -1323,10 +1344,15 @@ function createFrameRenderer(Skia) {
1323
1344
  const paint = effect === void 0 ? defaultPaint : resolveEffectPaint(effect);
1324
1345
  if (blendAdditive) paint.setBlendMode?.(BLEND_PLUS);
1325
1346
  paint.setAlphaf(alpha);
1326
- current.drawAtlas(image, srcs, dsts, paint);
1347
+ drawAtlasSampled(current, image, srcs, dsts, paint, sampling);
1327
1348
  paint.setAlphaf(1);
1328
1349
  if (blendAdditive) paint.setBlendMode?.(BLEND_SRC_OVER);
1329
1350
  },
1351
+ setImageSampling(image, sampling) {
1352
+ if (sampling === "nearest") imageSampling.set(image, SAMPLING_NEAREST);
1353
+ else if (sampling === "linear") imageSampling.set(image, SAMPLING_LINEAR);
1354
+ else throw new RangeError(`setImageSampling: unknown sampling '${String(sampling)}' (expected 'nearest' or 'linear')`);
1355
+ },
1330
1356
  setPostFxBinding(runtime, chain) {
1331
1357
  _boundRuntime = runtime;
1332
1358
  _boundChain = chain;
@@ -1334,16 +1360,9 @@ function createFrameRenderer(Skia) {
1334
1360
  hasPostFxBinding() {
1335
1361
  return _boundRuntime !== null && !!_boundRuntime.supported && _boundRuntime.buildPass !== void 0 && _boundChain !== null;
1336
1362
  },
1363
+ compilePostFxPasses,
1337
1364
  buildPostFxPasses(runtime, chain, width, height) {
1338
- const rt = runtime ?? _boundRuntime;
1339
- const ch = chain ?? _boundChain;
1340
- if (rt === null || !rt.supported) return [];
1341
- if (rt.buildPass === void 0) {
1342
- logOnce("buildPostFxPasses: runtime.supported but buildPass absent — no passes");
1343
- return [];
1344
- }
1345
- if (ch === null) return [];
1346
- const passes = rt.buildPass(ch, width, height);
1365
+ const passes = compilePostFxPasses(runtime, chain, width, height);
1347
1366
  if (passes.length === 0) return [];
1348
1367
  const out = [];
1349
1368
  for (const pass of passes) {
@@ -1404,6 +1423,15 @@ function getXformCounters() {
1404
1423
  unrotated: _xformUnrotatedCount
1405
1424
  };
1406
1425
  }
1426
+ /** Without a sampling setting this is exactly the 4-argument call the surface always received. */
1427
+ function drawAtlasSampled(cvs, image, srcs, dsts, paint, sampling) {
1428
+ if (sampling === void 0) cvs.drawAtlas(image, srcs, dsts, paint);
1429
+ else cvs.drawAtlas(image, srcs, dsts, paint, void 0, void 0, sampling);
1430
+ }
1431
+ function drawImageRectSampled(cvs, image, src, dst, paint, sampling) {
1432
+ if (sampling === void 0 || cvs.drawImageRectOptions === void 0) cvs.drawImageRect(image, src, dst, paint);
1433
+ else cvs.drawImageRectOptions(image, src, dst, sampling.filter, MIPMAP_NONE, paint);
1434
+ }
1407
1435
  function writeColor(out, color, alphaMul) {
1408
1436
  out[0] = color.r;
1409
1437
  out[1] = color.g;