Commit c7bf779f authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-B rung B4 - staged pinned bayer upload in the JNA backend

setBayerImages (both float[][] and double[][][] variants) now funnels
through uploadBayer: each sensor image is written into the pinned
native staging slot (one JNI region copy) and committed with
tp_proc_set_image_staged - the async pinned->device DMA of sensor k
overlaps the convert/write of sensor k+1 (per-sensor copy/compute
pipelining, design B4). Double-buffered slots flip per upload (RT
inter-scene overlap provision). Falls back one-time to the legacy
pageable path on missing natives or any staged failure (legacy fences
first, so a partial staged upload is always fully rewritten).

ONE-SHOT ORACLE (first staged upload): reads every uploaded sensor
back and counts bit-mismatches vs the Java array - EXPECT 0 EXACTLY,
plus the enqueue-side upload ms. Requires tile_processor_gpu 11ccdc2
(libtileproc.so rebuild); older libs downgrade gracefully.

mvn PASS; native run_cases.sh ALL PASS incl. new bayer_staged case.
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 53ba6631
...@@ -300,19 +300,99 @@ public class GpuQuadJna extends GpuQuad { ...@@ -300,19 +300,99 @@ public class GpuQuadJna extends GpuQuad {
setBayerImages(quadCLT.getResetImageData(), true); setBayerImages(quadCLT.getResetImageData(), true);
} }
@Override public void setBayerImages(double[][][] bayer_data, boolean force) { @Override public void setBayerImages(double[][][] bayer_data, boolean force) {
for (int ncam = 0; ncam < bayer_data.length; ncam++) { uploadBayer(bayer_data, null); // By Claude on 07/16/2026 (rung B4: staged path + legacy fallback)
float[] f = combineChannels(bayer_data[ncam]);
if (f != null) lib.tp_proc_set_image(proc, ncam, f);
}
jna_bayer_set = true; // By Claude on 07/02/2026: match base (gpuTileProcessor.bayer_set = true) jna_bayer_set = true; // By Claude on 07/02/2026: match base (gpuTileProcessor.bayer_set = true)
} }
@Override public void setBayerImages(float[][] bayer_data, boolean force) { @Override public void setBayerImages(float[][] bayer_data, boolean force) {
if (jna_bayer_set && !force) return; if (jna_bayer_set && !force) return;
for (int ncam = 0; ncam < bayer_data.length; ncam++) { uploadBayer(null, bayer_data); // By Claude on 07/16/2026 (rung B4: staged path + legacy fallback)
if (bayer_data[ncam] != null) lib.tp_proc_set_image(proc, ncam, bayer_data[ncam]);
}
jna_bayer_set = true; jna_bayer_set = true;
} }
// ---- 3-B rung B4 (design 2026-07-16): pinned staged bayer upload. By Claude on 07/16/2026. ----
// The legacy per-sensor tp_proc_set_image is a synchronous cudaMemcpy2D from PAGEABLE
// JNA-marshalled memory (B0: 11.9 ms/scene for 16 x 327680 px = ~1.8 GB/s). The staged
// path writes each sensor image into a pinned native staging slot (one JNI region copy)
// and enqueues the pinned->device copy on a dedicated non-blocking stream - sensor k
// DMAs while sensor k+1 is converted/written (per-sensor copy/compute pipelining).
// Native consumers of the resident images fence before touching them, so downstream
// is byte-identical (one-shot readback oracle below). Double-buffered slots (flip per
// upload) = the RT-service inter-scene overlap provision. Falls back to the legacy
// path if the staged natives are missing (older libtileproc.so) or a call fails.
private final Pointer [] bayer_staging = new Pointer [2];
private int bayer_staging_slot = 0;
private boolean bayer_staged_ok = true; // one-time downgrade on missing natives/failure
private static boolean b4_oracle_reported = false; // one-shot bit-exact readback gate
private void uploadBayer(double[][][] dd, float[][] ff) {
final int ncams = (dd != null) ? dd.length : ff.length;
if (bayer_staged_ok && uploadBayerStaged(dd, ff, ncams)) return;
for (int ncam = 0; ncam < ncams; ncam++) { // legacy pageable path
float[] f = (dd != null) ? combineChannels(dd[ncam]) : ff[ncam];
if (f != null) lib.tp_proc_set_image(proc, ncam, f);
}
}
private boolean uploadBayerStaged(double[][][] dd, float[][] ff, int ncams) {
final int npix = img_width * img_height;
try {
final int slot = bayer_staging_slot;
if (bayer_staging[slot] == null) {
bayer_staging[slot] = lib.tp_proc_bayer_staging(proc, slot);
if (bayer_staging[slot] == null) {
System.out.println("GpuQuadJna: pinned bayer staging unavailable ("+
lib.tp_last_error()+") - using the legacy pageable upload");
bayer_staged_ok = false;
return false;
}
}
final Pointer staging = bayer_staging[slot];
final boolean oracle = !b4_oracle_reported;
final float [][] oracle_src = oracle ? new float [ncams][] : null;
final long t0 = oracle ? System.nanoTime() : 0L;
for (int ncam = 0; ncam < ncams; ncam++) {
// convert/write sensor k+1 while sensor k's async DMA is in flight
float[] f = (dd != null) ? combineChannels(dd[ncam]) : ff[ncam];
if (f == null) continue;
if (f.length != npix) { // unexpected shape - keep this call fully legacy
System.out.println("GpuQuadJna: staged upload shape mismatch (cam "+ncam+
": "+f.length+" vs "+npix+" px) - legacy upload for this call");
return false;
}
staging.write((long) ncam * npix * 4L, f, 0, npix);
int rc = lib.tp_proc_set_image_staged(proc, ncam, slot);
if (rc != 0) {
System.out.println("GpuQuadJna: tp_proc_set_image_staged rc="+rc+" ("+
lib.tp_last_error()+") - reverting to the legacy pageable upload");
bayer_staged_ok = false;
return false; // legacy fallback re-uploads ALL sensors (fences first)
}
if (oracle) oracle_src[ncam] = f;
}
bayer_staging_slot = slot ^ 1; // double-buffer flip (inter-scene overlap provision)
if (oracle) { // one-shot B4 gate: staged upload must be BIT-identical on the GPU
b4_oracle_reported = true;
final double upload_ms = (System.nanoTime() - t0) * 1e-6;
long mismatches = 0; int checked = 0;
final float [] back = new float [npix];
for (int ncam = 0; ncam < ncams; ncam++) {
if (oracle_src[ncam] == null) continue;
if (lib.tp_proc_get_image(proc, ncam, back) != 0) { mismatches = -1; break; }
for (int i = 0; i < npix; i++) {
if (Float.floatToRawIntBits(back[i]) != Float.floatToRawIntBits(oracle_src[ncam][i])) mismatches++;
}
checked++;
}
System.out.println(String.format(
"GpuQuadJna B4 staged-upload oracle (one-shot): %d sensors x %d px in %.3f ms,"+
" readback bit-mismatches=%d -> %s",
checked, npix, upload_ms, mismatches, (mismatches == 0) ? "PASS" : "FAIL"));
}
return true;
} catch (UnsatisfiedLinkError e) {
System.out.println("GpuQuadJna: staged bayer natives missing ("+e.getMessage()+
") - using the legacy pageable upload");
bayer_staged_ok = false;
return false;
}
}
// sum the (1 or 3) split-color channels into one image, as GpuQuad.setBayerImages does // sum the (1 or 3) split-color channels into one image, as GpuQuad.setBayerImages does
private static float[] combineChannels(double[][] chans) { private static float[] combineChannels(double[][] chans) {
if (chans == null || chans[0] == null) return null; if (chans == null || chans[0] == null) return null;
......
...@@ -73,6 +73,14 @@ public interface TpJna extends Library { ...@@ -73,6 +73,14 @@ public interface TpJna extends Library {
int tp_proc_set_kernels(Pointer proc, int cam, float[] d, int n); int tp_proc_set_kernels(Pointer proc, int cam, float[] d, int n);
int tp_proc_set_kernel_offsets(Pointer proc, int cam, float[] d, int n); int tp_proc_set_kernel_offsets(Pointer proc, int cam, float[] d, int n);
int tp_proc_set_image(Pointer proc, int cam, float[] d); int tp_proc_set_image(Pointer proc, int cam, float[] d);
// ---- 3-B rung B4: pinned staged bayer upload (async per-sensor H2D). By Claude on 07/16/2026. ----
/** Pinned host staging for slot (0/1): num_cams*img_w*img_h floats, lazily allocated
* together with the dedicated non-blocking copy stream + fence event. Null on failure. */
Pointer tp_proc_bayer_staging(Pointer proc, int slot);
/** Enqueue the pinned->device async copy of ONE sensor image from staging slot and
* return immediately (per-sensor copy/compute pipelining). Image consumers fence
* natively, so a partial upload is never visible to kernels or readbacks. */
int tp_proc_set_image_staged(Pointer proc, int cam, int slot);
/** De-pitch one resident source image into out[width*height]. */ /** De-pitch one resident source image into out[width*height]. */
int tp_proc_get_image(Pointer proc, int cam, float[] out); int tp_proc_get_image(Pointer proc, int cam, float[] out);
/** Resident production calibration maps, contiguous [camera][height][width]. */ /** Resident production calibration maps, contiguous [camera][height][width]. */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment