@photivra/engine 2.6.0 → 2.7.0
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/CHANGELOG.md +92 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +3 -2
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/chart-scenes.d.ts +2 -0
- package/dist/facade/chart-scenes.js +4 -3
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/display-view.d.ts +5 -0
- package/dist/facade/display-view.js +134 -52
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/scene-radiance.d.ts +38 -5
- package/dist/facade/scene-radiance.js +79 -23
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +14 -6
- package/dist/facade/simulate-capture.js +19 -7
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +24 -0
- package/dist/facade/simulate-display-view.js +101 -55
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/API_REFERENCE.md +7 -1
- package/docs/QUICK_START.md +48 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +32 -2
- package/docs/api/facade-scene-radiance.md +34 -1
- package/docs/api/facade-simulate-capture.md +1 -1
- package/docs/api/facade-simulate-display-view.md +39 -0
- package/package.json +1 -1
|
@@ -390,8 +390,35 @@ export function createDisplayViewExpectedSignal(input) {
|
|
|
390
390
|
ringMaterial[q] = lastMaterial;
|
|
391
391
|
return v;
|
|
392
392
|
}
|
|
393
|
-
|
|
393
|
+
// Without diffraction (#392): site values persist across windows the same way, in their own ring (a site's value does not
|
|
394
|
+
// depend on the window). A ring hit counts the lookups directSite would have made, so `lookups` is unchanged.
|
|
395
|
+
let directRows = 0, directOn = false, directRing = new Float64Array(0), directRowId = new Int32Array(0);
|
|
396
|
+
const directLookups = offsets.length * (scene.kind === "materials" ? 1 : nodes);
|
|
397
|
+
function directRead(x, y) {
|
|
398
|
+
const slot = y & (directRows - 1), q = slot * W + x;
|
|
399
|
+
if (directRowId[slot] === y) {
|
|
400
|
+
const v = directRing[q];
|
|
401
|
+
if (v === v) {
|
|
402
|
+
lookups += directLookups;
|
|
403
|
+
return v;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
directRowId[slot] = y;
|
|
408
|
+
directRing.fill(NaN, slot * W, (slot + 1) * W);
|
|
409
|
+
}
|
|
410
|
+
const v = directSite(x, y);
|
|
411
|
+
directRing[q] = v;
|
|
412
|
+
return v;
|
|
413
|
+
}
|
|
414
|
+
/** A site not in the window's site cache: from a ring, a filled window without the ring, the lazy window caches, or uncached. */
|
|
394
415
|
function siteMiss(x, y, i, j, cached) {
|
|
416
|
+
if (directOn) {
|
|
417
|
+
const v = directRead(x, y);
|
|
418
|
+
if (cached)
|
|
419
|
+
siteCache[j * sw + i] = v;
|
|
420
|
+
return v;
|
|
421
|
+
}
|
|
395
422
|
if (ringOn) {
|
|
396
423
|
if (!cached) {
|
|
397
424
|
outsideRead(i, j);
|
|
@@ -480,6 +507,7 @@ export function createDisplayViewExpectedSignal(input) {
|
|
|
480
507
|
get lookups() { return lookups + (filled ? windowLookups() : 0); },
|
|
481
508
|
get uniformMaterial() { return lastMaterial; },
|
|
482
509
|
get tracksMaterial() { return ringOn; },
|
|
510
|
+
get cachesSites() { return sw > 0; },
|
|
483
511
|
window(x0, y0, x1, y1) {
|
|
484
512
|
if (filled)
|
|
485
513
|
lookups += windowLookups();
|
|
@@ -497,8 +525,21 @@ export function createDisplayViewExpectedSignal(input) {
|
|
|
497
525
|
if (siteCache.length < sw * sh)
|
|
498
526
|
siteCache = new Float64Array(sw * sh);
|
|
499
527
|
siteCache.fill(NaN, 0, sw * sh);
|
|
500
|
-
if (!input.kernels)
|
|
528
|
+
if (!input.kernels) {
|
|
529
|
+
// A ring of at least twice the window's rows keeps the rows of the previous display row's windows.
|
|
530
|
+
if (sh > 0 && directRows < 2 * sh) {
|
|
531
|
+
let rows = 1;
|
|
532
|
+
while (rows < 2 * sh)
|
|
533
|
+
rows *= 2;
|
|
534
|
+
if (rows * W <= CACHE_LIMIT) {
|
|
535
|
+
directRows = rows;
|
|
536
|
+
directRing = new Float64Array(rows * W).fill(NaN);
|
|
537
|
+
directRowId = new Int32Array(rows).fill(-1);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
directOn = sh > 0 && directRows >= 2 * sh;
|
|
501
541
|
return;
|
|
542
|
+
}
|
|
502
543
|
for (const group of groups) {
|
|
503
544
|
group.cx0 = x0 + group.minIx - R;
|
|
504
545
|
group.cy0 = y0 + group.minIy - R;
|
|
@@ -841,6 +882,8 @@ function correctionStates(key) {
|
|
|
841
882
|
* AHD choice terms; 33 a 1 when the AHD correction applies and [34, 37) its values.
|
|
842
883
|
*/
|
|
843
884
|
const MEMO_SIZE = 37;
|
|
885
|
+
/** Most real pixels a display pixel's footprint may have for the per-real-pixel memo (#392); larger footprints rarely repeat a real pixel. */
|
|
886
|
+
const PIXEL_MEMO_LIMIT = 1 << 16;
|
|
844
887
|
/** Pilot points per axis: about one per two sites of the footprint plus its reach, 4 to 16. */
|
|
845
888
|
function pilotCount(span) { return Math.max(4, Math.min(16, Math.ceil(span / 2))); }
|
|
846
889
|
/** Renders the display view (docs/DISPLAY_VIEW.md). */
|
|
@@ -1063,6 +1106,13 @@ export async function renderDisplayView(setup) {
|
|
|
1063
1106
|
let siteCount = 0, sampleCount = 0, detail = 0, uniform = 0, lastYield = Date.now();
|
|
1064
1107
|
/** Recorded terms of samples whose sites all read one chart material, by material·4 + CFA phase (layout: MEMO_SIZE). */
|
|
1065
1108
|
const sampleMemo = new Map();
|
|
1109
|
+
// Per-real-pixel memo (#392): a sample's recorded terms depend only on its real pixel (its sites' expected electrons and its
|
|
1110
|
+
// CFA phase), not on the display pixel or the sample, so a later sample of the same real pixel in the same display pixel
|
|
1111
|
+
// replays them (the same values added to the same sums in the same order, as the one-material memo does). Slots cover the
|
|
1112
|
+
// display pixel's footprint and are valid while their stamp equals the display pixel's generation. A replayed sample still
|
|
1113
|
+
// counts its sites; it is used only where every site lies in the window's site cache, where reading it again would have
|
|
1114
|
+
// cost no work and no scene lookup.
|
|
1115
|
+
let pixelStamp = new Int32Array(0), pixelRecords = new Float64Array(0), pixelGeneration = 0;
|
|
1066
1116
|
// Pilot: the scene at a deterministic grid over each display pixel's footprint widened by the reach; a pixel is uniform when
|
|
1067
1117
|
// every point returns the same value. Pixels next to a detailed pixel count as detailed too, so an edge that only clips a
|
|
1068
1118
|
// footprint's corner (too thin for the grid) still gets the full samples. The grid is fixed, so the sample count is not
|
|
@@ -1118,6 +1168,17 @@ export async function renderDisplayView(setup) {
|
|
|
1118
1168
|
detail++;
|
|
1119
1169
|
setup.signal.window(Math.floor(vx * sx) + margin - windowReach, Math.floor(vy * sy) + margin - windowReach, Math.ceil((vx + 1) * sx) + margin + windowReach, Math.ceil((vy + 1) * sy) + margin + windowReach);
|
|
1120
1170
|
const jitter = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, index, DISPLAY_VIEW_JITTER_STREAM_ID)), tracking = setup.signal.tracksMaterial;
|
|
1171
|
+
const mx0 = Math.floor(vx * sx) + margin, my0 = Math.floor(vy * sy) + margin;
|
|
1172
|
+
const mw = Math.min(frameWidth - 1, Math.ceil((vx + 1) * sx)) + margin - mx0 + 1, mh = Math.min(frameHeight - 1, Math.ceil((vy + 1) * sy)) + margin - my0 + 1;
|
|
1173
|
+
const pixelMemo = setup.signal.cachesSites && mw * mh <= PIXEL_MEMO_LIMIT;
|
|
1174
|
+
if (pixelMemo) {
|
|
1175
|
+
if (pixelStamp.length < mw * mh) {
|
|
1176
|
+
pixelStamp = new Int32Array(mw * mh);
|
|
1177
|
+
pixelRecords = new Float64Array(mw * mh * MEMO_SIZE);
|
|
1178
|
+
pixelGeneration = 0;
|
|
1179
|
+
}
|
|
1180
|
+
pixelGeneration++;
|
|
1181
|
+
}
|
|
1121
1182
|
let s0 = 0, s1 = 0, s2 = 0, c00 = 0, c01 = 0, c02 = 0, c11 = 0, c12 = 0, c22 = 0, r0 = 0, r1 = 0, r2 = 0, pixelVariance = 0, corrected = false;
|
|
1122
1183
|
for (let b = 0; b < K; b++)
|
|
1123
1184
|
for (let a = 0; a < K; a++) {
|
|
@@ -1125,53 +1186,69 @@ export async function renderDisplayView(setup) {
|
|
|
1125
1186
|
const x = Math.min(frameWidth - 1, Math.floor(fx)) + margin, y = Math.min(frameHeight - 1, Math.floor(fy)) + margin;
|
|
1126
1187
|
const phase = (y & 1) * 2 + (x & 1), list = phaseList[phase];
|
|
1127
1188
|
let material = -2;
|
|
1189
|
+
const px = x - mx0, py = y - my0;
|
|
1190
|
+
const pk = pixelMemo && px >= 0 && py >= 0 && px < mw && py < mh && x >= reach && y >= reach && x + reach < nativeWidth && y + reach < nativeHeight ? py * mw + px : -1;
|
|
1191
|
+
let memo, mo = 0;
|
|
1192
|
+
if (pk >= 0 && pixelStamp[pk] === pixelGeneration) {
|
|
1193
|
+
memo = pixelRecords;
|
|
1194
|
+
mo = pk * MEMO_SIZE;
|
|
1195
|
+
siteCount += list.length;
|
|
1196
|
+
}
|
|
1128
1197
|
// The expected electrons of the sites the demosaic reads (sites outside the raster, at mirror borders, are read
|
|
1129
1198
|
// reflect-101); where the signal tracks it, whether they all read one chart material, with the statistics below only
|
|
1130
1199
|
// when the sample is not replayed; otherwise the statistics at once.
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
sxs
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
sys
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1200
|
+
if (!memo)
|
|
1201
|
+
for (let li = 0; li < list.length; li++) {
|
|
1202
|
+
const slot = list[li];
|
|
1203
|
+
siteCount++;
|
|
1204
|
+
let sxs = x + (slot % side) - reach, sys = y + ((slot / side) | 0) - reach;
|
|
1205
|
+
if (sxs < 0)
|
|
1206
|
+
sxs = -sxs;
|
|
1207
|
+
else if (sxs >= nativeWidth)
|
|
1208
|
+
sxs = 2 * nativeWidth - 2 - sxs;
|
|
1209
|
+
if (sys < 0)
|
|
1210
|
+
sys = -sys;
|
|
1211
|
+
else if (sys >= nativeHeight)
|
|
1212
|
+
sys = 2 * nativeHeight - 2 - sys;
|
|
1213
|
+
const c = (sys & 1) === 0 ? ((sxs & 1) === 0 ? 0 : 1) : ((sxs & 1) === 0 ? 1 : 2);
|
|
1214
|
+
const e = setup.signal.site(sxs, sys) + dark[c];
|
|
1215
|
+
siteChannel[slot] = c;
|
|
1216
|
+
if (tracking) {
|
|
1217
|
+
siteElectrons[slot] = e;
|
|
1218
|
+
const own = setup.signal.uniformMaterial;
|
|
1219
|
+
material = material === -2 || material === own ? own : -1;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
// As siteStatistics, written out here for the untracked (Speed) path.
|
|
1223
|
+
const kc = k[c], mean = kc * e, variance = kc * kc * e + c0[c], gap = 1 - mean;
|
|
1224
|
+
siteThird[slot] = kc * kc * kc * e;
|
|
1225
|
+
if (gap > 0 && gap * gap > reach2 * variance) {
|
|
1226
|
+
siteMean[slot] = mean;
|
|
1227
|
+
siteVar[slot] = variance;
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
upperClip(mean, variance, 1, clip);
|
|
1231
|
+
siteMean[slot] = clip[0];
|
|
1232
|
+
siteVar[slot] = clip[1];
|
|
1233
|
+
}
|
|
1163
1234
|
}
|
|
1164
|
-
}
|
|
1165
1235
|
if (!tracking)
|
|
1166
1236
|
material = -1;
|
|
1167
1237
|
// A sample whose sites all read one chart material only has the statistics of every other such sample of that material
|
|
1168
1238
|
// and CFA phase: replay its recorded terms (the same values added to the same sums in the same order).
|
|
1169
|
-
const memoKey = material >= 0 ? material * 4 + phase : -1
|
|
1239
|
+
const memoKey = !memo && material >= 0 ? material * 4 + phase : -1;
|
|
1240
|
+
if (memoKey >= 0) {
|
|
1241
|
+
memo = sampleMemo.get(memoKey);
|
|
1242
|
+
if (memo && pk >= 0) {
|
|
1243
|
+
pixelRecords.set(memo, pk * MEMO_SIZE);
|
|
1244
|
+
pixelStamp[pk] = pixelGeneration;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1170
1247
|
const cs = candidateSlots[phase], cw = candidateWeights[phase], count = cs.length, share = 1 / count;
|
|
1171
1248
|
if (memo) {
|
|
1172
1249
|
for (let d = 0; d < count; d++) {
|
|
1173
1250
|
for (let c = 0; c < 3; c++) {
|
|
1174
|
-
const o = 3 + (d * 3 + c) * 3, a0 = memo[o], a1 = memo[o + 1], a2 = memo[o + 2], wgt = share * B[c] * memo[c];
|
|
1251
|
+
const o = mo + 3 + (d * 3 + c) * 3, a0 = memo[o], a1 = memo[o + 1], a2 = memo[o + 2], wgt = share * B[c] * memo[mo + c];
|
|
1175
1252
|
c00 += wgt * a0 * a0;
|
|
1176
1253
|
c01 += wgt * a0 * a1;
|
|
1177
1254
|
c02 += wgt * a0 * a2;
|
|
@@ -1179,27 +1256,27 @@ export async function renderDisplayView(setup) {
|
|
|
1179
1256
|
c12 += wgt * a1 * a2;
|
|
1180
1257
|
c22 += wgt * a2 * a2;
|
|
1181
1258
|
}
|
|
1182
|
-
r0 += memo[21 + d * 3];
|
|
1183
|
-
r1 += memo[22 + d * 3];
|
|
1184
|
-
r2 += memo[23 + d * 3];
|
|
1259
|
+
r0 += memo[mo + 21 + d * 3];
|
|
1260
|
+
r1 += memo[mo + 22 + d * 3];
|
|
1261
|
+
r2 += memo[mo + 23 + d * 3];
|
|
1185
1262
|
}
|
|
1186
|
-
s0 += memo[27];
|
|
1187
|
-
s1 += memo[28];
|
|
1188
|
-
s2 += memo[29];
|
|
1263
|
+
s0 += memo[mo + 27];
|
|
1264
|
+
s1 += memo[mo + 28];
|
|
1265
|
+
s2 += memo[mo + 29];
|
|
1189
1266
|
if (count === 2) {
|
|
1190
|
-
r0 += memo[30];
|
|
1191
|
-
r1 += memo[31];
|
|
1192
|
-
r2 += memo[32];
|
|
1267
|
+
r0 += memo[mo + 30];
|
|
1268
|
+
r1 += memo[mo + 31];
|
|
1269
|
+
r2 += memo[mo + 32];
|
|
1193
1270
|
}
|
|
1194
|
-
if (memo[33] === 1) {
|
|
1195
|
-
s0 += memo[34];
|
|
1196
|
-
s1 += memo[35];
|
|
1197
|
-
s2 += memo[36];
|
|
1271
|
+
if (memo[mo + 33] === 1) {
|
|
1272
|
+
s0 += memo[mo + 34];
|
|
1273
|
+
s1 += memo[mo + 35];
|
|
1274
|
+
s2 += memo[mo + 36];
|
|
1198
1275
|
corrected = true;
|
|
1199
1276
|
}
|
|
1200
1277
|
continue;
|
|
1201
1278
|
}
|
|
1202
|
-
const record = memoKey >= 0 ? new Float64Array(MEMO_SIZE) : undefined;
|
|
1279
|
+
const record = memoKey >= 0 ? new Float64Array(MEMO_SIZE) : pk >= 0 ? pixelRecords.subarray(pk * MEMO_SIZE, (pk + 1) * MEMO_SIZE).fill(0) : undefined;
|
|
1203
1280
|
if (tracking)
|
|
1204
1281
|
for (let li = 0; li < list.length; li++) {
|
|
1205
1282
|
const slot = list[li];
|
|
@@ -1391,8 +1468,13 @@ export async function renderDisplayView(setup) {
|
|
|
1391
1468
|
record[36] = fix[2];
|
|
1392
1469
|
}
|
|
1393
1470
|
}
|
|
1394
|
-
if (record)
|
|
1471
|
+
if (record && memoKey >= 0) {
|
|
1395
1472
|
sampleMemo.set(memoKey, record);
|
|
1473
|
+
if (pk >= 0)
|
|
1474
|
+
pixelRecords.set(record, pk * MEMO_SIZE);
|
|
1475
|
+
}
|
|
1476
|
+
if (pk >= 0)
|
|
1477
|
+
pixelStamp[pk] = pixelGeneration;
|
|
1396
1478
|
pixelVariance = 0;
|
|
1397
1479
|
}
|
|
1398
1480
|
sampleCount += Q;
|