Commit ef36688d authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: 3-B rung B1 - pose_task_update resident task-build kernel

New NVRTC kernel pose_task_update: one thread per selected task template
entry runs the C1-validated projection chain (pose_world_from_pixel ->
pose_pixel_from_world) and rewrites the 6-float task headers of BOTH
resident ftask slots in place (slot 0 = MB main, slot 1 = MB partner).
Inputs are all resident: per-sequence template (packed txy + reference
centers, configuration-register semantics), per-scene camera blocks
(shared with prepare_resident via pose_prep_ready bits), the
device-authoritative current pose set, and per-scene scalars (uniform-MB
6-float descriptor + margins + pre-encoded |511 task words). Failed
projection / margin-gate tiles become task=0 HOLES in both sets -
index_direct, index_consolidate and the inter correlation all skip them
(the Java condensed stream reached the same end by omission).

JNA API: tp_proc_exec_pose_task_update (nullable groups, rung C2
contract; task_size derived from the uploaded skeleton) +
tp_proc_activate_tasks_slot (make a resident slot the live task stream
with no H2D - geometry/convert consume the kernel-rewritten slots).

Tests (test_avg_td_oob two-tier pairing precedent):
- src/tests/test_pose_task_update (nvcc-direct): grid-stride body makes
  a <<<1,1>>> launch the serial float oracle - parallel launch memcmp
  bit-exact; centerXY == pose_fx_jacobian fx + descriptor at 0 px
  (bit-identical across kernels); host-replayed margin/hole logic exact;
  xy/disp_dist tails untouched. PASS.
- jna/test_pose_task_update_jna (production NVRTC module): fx-consistent
  at 0 px, keep-resident rerun memcmp-identical, no-MB encoding
  (sub=511), activate-slot getter semantics, error paths. PASS.
All regressions PASS: run_cases.sh (pose_corr @tol 0 replay, peak,
peak_recenter, avg_td, avg_td_oob) + pose fx/step/resident/DP/products
standalone tests against the rebuilt module. compute-sanitizer cannot
run on this GPU (sm_120 "Device not supported" - environment, not code).

Design: imagej-elphel-internal
handoffs/2026-07-16_3b_measure_chain_residency_design.md (B1; gate D3).
Co-Authored-By: 's avatarClaude Fable 5 <noreply@anthropic.com>
parent 978c5193
// test_pose_task_update_jna.cu - NVRTC/TpProc tier gate for the 3-B rung B1
// resident task build (pairs with the nvcc-direct src/tests/test_pose_task_update.cu,
// the test_avg_td_oob pairing precedent). Drives the PRODUCTION module through
// the JNA C API: skeleton upload -> pose_task_update -> slot readback, checked
// against the C1-validated tp_proc_exec_pose_fx_jacobian fx readback plus a
// host replay of the margin gate. Also gates the rung-C2-style configuration
// register semantics (NULL groups = keep resident, error when absent), the
// no-MB encoding, and tp_proc_activate_tasks_slot.
//
// Build after jna/build_lib.sh:
// cd tile_processor_gpu && /usr/local/cuda-12.8/bin/nvcc -std=c++17 -O2 \
// jna/test_pose_task_update_jna.cu -o tests_bin/test_pose_task_update_jna \
// -L jna -ltileproc -Xlinker -rpath -Xlinker $PWD/jna
// Run: tests_bin/test_pose_task_update_jna [srcdir] [libcudadevrt.a]
//
// Created Jul 16, 2026 by Claude (Anthropic) for Elphel - 3-B rung B1.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
extern "C" {
void* tp_create_module(const char*, const char*);
const char* tp_last_error();
void* tp_proc_create(void*);
int tp_proc_setup(void*, int,int,int,int,int,int);
int tp_proc_set_tasks_slot(void*, const float*, int, int, int);
int tp_proc_get_tasks_slot(void*, float*, int, int);
int tp_proc_get_tasks(void*, float*, int);
int tp_proc_activate_tasks_slot(void*, int);
int tp_proc_exec_pose_fx_jacobian(
void*, const float*,const float*,const float*,int,const float*,int,
const float*,const float*,const float*,int,const float*,int,
const float*,const float*,const unsigned char*,int,int,
float*,float*,unsigned char*);
int tp_proc_exec_pose_task_update(
void*, const float*,const float*,const float*,int,const float*,int,
const float*,const float*,const float*,int,const float*,int,
const float*,const int*,const float*,int,int,
float,float,float,float,float,int,int,
float,float,float,float,float,float);
void tp_proc_destroy(void*);
void tp_destroy_module(void*);
}
int main(int argc, char** argv)
{
const char* srcdir=(argc>1)?argv[1]:"src";
const char* devrt=(argc>2)?argv[2]:
"/usr/local/cuda-12.8/targets/x86_64-linux/lib/libcudadevrt.a";
void* module=tp_create_module(srcdir,devrt);
if(!module){ std::fprintf(stderr,"tp_create_module: %s\n",tp_last_error()); return EXIT_FAILURE; }
void* proc=tp_proc_create(module);
if(!proc || tp_proc_setup(proc,1,1,16,16,0,0)){
std::fprintf(stderr,"tp_proc setup: %s\n",tp_last_error());
if(proc) tp_proc_destroy(proc); tp_destroy_module(module); return EXIT_FAILURE;
}
// Identity synthetic camera (test_pose_fx_jacobian_jna precedent).
const int width=64,height=48,rbr_len=256,ers_len=height*14;
const float meta[8]={(float)width,(float)height,4.0f,5.0f,2.85f,1.0f,0.0f,0.0004f};
const float radial[7]={0,0,0,0,0,0,0};
std::vector<float> rbr(rbr_len,1.0f),ers(ers_len,0.0f);
for(int line=0;line<height;line++) ers[(size_t)line*14+6]=1.0f;
const float pose[12]={0,0,0, 0,0,0, 0,0,0, 0,0,0};
const int num_tasks=61,num_cams=1,task_size=6+2*num_cams+4*num_cams;
std::vector<int> map(num_tasks);
std::vector<float> centers(3*num_tasks);
for(int i=0;i<num_tasks;i++){
map[i]=(i%8)|((i/8)<<16);
float px=3.0f+(i%59)*1.05f, py=2.0f+(i%43)*1.1f;
float disparity=(i%5==0)?0.0f:(0.5f+(i%7));
if(i%29==7) px=nanf("");
centers[3*i]=px; centers[3*i+1]=py; centers[3*i+2]=disparity;
}
const float disparity_corr=0.125f;
const float min_px=2.0f,max_px=width-3.0f,min_py=2.0f,max_py=height-3.0f;
const int code_main=((1<<9)|(1<<10))|511; // TASK_CORR_EN|TASK_INTER_EN, pre-|511
const float dx_main=-0.25f,dy_main=-0.1f,dx_sub=0.75f,dy_sub=0.3f;
const float scale_main=2.5f,scale_sub=-1.5f;
const int total_floats=num_tasks*task_size;
int bad=0;
// Error path 1: no skeleton in the slots yet.
int rc=tp_proc_exec_pose_task_update(proc,
meta,radial,rbr.data(),rbr_len,ers.data(),ers_len,
meta,radial,rbr.data(),rbr_len,ers.data(),ers_len,
pose,map.data(),centers.data(),num_tasks,num_cams,
disparity_corr,min_px,max_px,min_py,max_py,code_main,code_main,
dx_main,dy_main,dx_sub,dy_sub,scale_main,scale_sub);
if(rc==0){ std::printf("missing skeleton not rejected\n"); bad++; }
// Per-sequence skeleton upload (both slots), then the full first call.
std::vector<float> skeleton(total_floats,0.0f);
if(tp_proc_set_tasks_slot(proc,skeleton.data(),num_tasks,total_floats,0)||
tp_proc_set_tasks_slot(proc,skeleton.data(),num_tasks,total_floats,1)){
std::fprintf(stderr,"set_tasks_slot: %s\n",tp_last_error()); bad++;
}
rc=tp_proc_exec_pose_task_update(proc,
meta,radial,rbr.data(),rbr_len,ers.data(),ers_len,
meta,radial,rbr.data(),rbr_len,ers.data(),ers_len,
pose,map.data(),centers.data(),num_tasks,num_cams,
disparity_corr,min_px,max_px,min_py,max_py,code_main,code_main,
dx_main,dy_main,dx_sub,dy_sub,scale_main,scale_sub);
if(rc){ std::fprintf(stderr,"pose_task_update rc=%d: %s\n",rc,tp_last_error()); bad++; }
std::vector<float> t0(total_floats),t1(total_floats);
if(tp_proc_get_tasks_slot(proc,t0.data(),total_floats,0)||
tp_proc_get_tasks_slot(proc,t1.data(),total_floats,1)){
std::fprintf(stderr,"get_tasks_slot: %s\n",tp_last_error()); bad++;
}
// fx reference through the production C1 chain (same centers/pose).
std::vector<unsigned char> sel(num_tasks,1),valid(num_tasks);
std::vector<float> fx(2*num_tasks);
rc=tp_proc_exec_pose_fx_jacobian(proc,
meta,radial,rbr.data(),rbr_len,ers.data(),ers_len,
meta,radial,rbr.data(),rbr_len,ers.data(),ers_len,
pose,centers.data(),sel.data(),num_tasks,0,fx.data(),nullptr,valid.data());
if(rc){ std::fprintf(stderr,"pose_fx_jacobian rc=%d: %s\n",rc,tp_last_error()); bad++; }
int survivors=0,holes=0; float max_err=0.0f;
for(int i=0;i<num_tasks&&!rc;i++){
const float* h0=&t0[(size_t)i*task_size];
const float* h1=&t1[(size_t)i*task_size];
if(*(const int*)(h0+1)!=map[i]||*(const int*)(h1+1)!=map[i]){
if(bad<5) std::printf("task %d: txy mismatch\n",i); bad++; }
bool expect=false;
if(valid[i]){
const float sx=fx[2*i],sy=fx[2*i+1];
const float cxm=sx+dx_main,cym=sy+dy_main,cxs=sx+dx_sub,cys=sy+dy_sub;
expect=!((cxm<min_px)||(cxm>max_px)||(cym<min_py)||(cym>max_py)||
(cxs<min_px)||(cxs>max_px)||(cys<min_py)||(cys>max_py));
if(expect){
float err=std::fabs(h0[3]-cxm);
err=std::fmax(err,std::fabs(h0[4]-cym));
err=std::fmax(err,std::fabs(h1[3]-cxs));
err=std::fmax(err,std::fabs(h1[4]-cys));
if(err>max_err) max_err=err;
if(err>1.0e-4f){ if(bad<5) std::printf("task %d: centerXY err %g\n",i,err); bad++; }
}
}
const int tw0=*(const int*)(h0+0),tw1=*(const int*)(h1+0);
if(expect){
if(tw0!=code_main||tw1!=code_main||h0[5]!=scale_main||h1[5]!=scale_sub||h0[2]!=h1[2]){
if(bad<5) std::printf("task %d: header mismatch (%08x/%08x)\n",i,tw0,tw1); bad++; }
else survivors++;
} else {
if(tw0!=0||tw1!=0||h0[2]!=0.0f||h1[2]!=0.0f||h0[3]!=0.0f||h0[4]!=0.0f||
h1[3]!=0.0f||h1[4]!=0.0f||h0[5]!=0.0f||h1[5]!=0.0f){
if(bad<5) std::printf("task %d: hole not zeroed (%08x/%08x)\n",i,tw0,tw1); bad++; }
else holes++;
}
}
if(survivors==0||holes==0){ std::printf("degenerate coverage %d/%d\n",survivors,holes); bad++; }
// Keep-resident semantics: all groups NULL (pose too) must reproduce the
// exact same streams (configuration registers, rung C2 contract).
rc=tp_proc_exec_pose_task_update(proc,
nullptr,nullptr,nullptr,0,nullptr,0,
nullptr,nullptr,nullptr,0,nullptr,0,
nullptr,nullptr,nullptr,num_tasks,num_cams,
disparity_corr,min_px,max_px,min_py,max_py,code_main,code_main,
dx_main,dy_main,dx_sub,dy_sub,scale_main,scale_sub);
if(rc){ std::fprintf(stderr,"keep-resident rc=%d: %s\n",rc,tp_last_error()); bad++; }
else {
std::vector<float> r0(total_floats),r1(total_floats);
tp_proc_get_tasks_slot(proc,r0.data(),total_floats,0);
tp_proc_get_tasks_slot(proc,r1.data(),total_floats,1);
if(std::memcmp(r0.data(),t0.data(),total_floats*sizeof(float))||
std::memcmp(r1.data(),t1.data(),total_floats*sizeof(float))){
std::printf("keep-resident rerun differs\n"); bad++; }
}
// No-MB encoding: zero descriptor, sub set disabled (511, the Java |511 of a
// zero task) - sub center equals the main center, sub task word 511.
rc=tp_proc_exec_pose_task_update(proc,
nullptr,nullptr,nullptr,0,nullptr,0, nullptr,nullptr,nullptr,0,nullptr,0,
nullptr,nullptr,nullptr,num_tasks,num_cams,
disparity_corr,min_px,max_px,min_py,max_py,code_main,511,
0.0f,0.0f,0.0f,0.0f,1.0f,-1.0e-12f);
if(rc){ std::fprintf(stderr,"no-MB rc=%d: %s\n",rc,tp_last_error()); bad++; }
else {
std::vector<float> r0(total_floats),r1(total_floats);
tp_proc_get_tasks_slot(proc,r0.data(),total_floats,0);
tp_proc_get_tasks_slot(proc,r1.data(),total_floats,1);
for(int i=0;i<num_tasks;i++){
const float* h0=&r0[(size_t)i*task_size]; const float* h1=&r1[(size_t)i*task_size];
const int tw0=*(const int*)(h0+0),tw1=*(const int*)(h1+0);
if(tw0==code_main){
if(tw1!=511||h1[3]!=h0[3]||h1[4]!=h0[4]||h0[5]!=1.0f){
if(bad<5) std::printf("no-MB task %d: sub encoding wrong (%08x)\n",i,tw1); bad++; }
} else if(tw0!=0||tw1!=0){ if(bad<5) std::printf("no-MB task %d: hole wrong\n",i); bad++; }
}
}
// activate_tasks_slot: the active-slot getter must follow without an upload.
if(tp_proc_activate_tasks_slot(proc,1)){ std::printf("activate(1) failed: %s\n",tp_last_error()); bad++; }
else {
std::vector<float> ra(total_floats),r1(total_floats);
tp_proc_get_tasks(proc,ra.data(),total_floats);
tp_proc_get_tasks_slot(proc,r1.data(),total_floats,1);
if(std::memcmp(ra.data(),r1.data(),total_floats*sizeof(float))){
std::printf("activate(1): active getter != slot 1\n"); bad++; }
}
if(tp_proc_activate_tasks_slot(proc,0)){ std::printf("activate(0) failed\n"); bad++; }
if(tp_proc_activate_tasks_slot(proc,2)==0){ std::printf("activate(2) accepted\n"); bad++; }
std::printf("NVRTC pose_task_update: %d tasks (%d survive, %d holes), fx-consistent "
"max err %g px, keep-resident + no-MB + activate -> %s\n",
num_tasks,survivors,holes,max_err,bad?"FAIL":"PASS");
tp_proc_destroy(proc); tp_destroy_module(module);
return bad?EXIT_FAILURE:EXIT_SUCCESS;
}
...@@ -54,6 +54,7 @@ static const char* KERNELS[] = { ...@@ -54,6 +54,7 @@ static const char* KERNELS[] = {
"clt_average_sensors","index_consolidate","consolidate_oob","clt_average_sensors_list", // By Claude on 07/12/2026 "clt_average_sensors","index_consolidate","consolidate_oob","clt_average_sensors_list", // By Claude on 07/12/2026
"corr2D_peak_eig","corr2D_peak_eig_f", // double + float instantiations (precision two-step) // By Claude on 07/13-14/2026 "corr2D_peak_eig","corr2D_peak_eig_f", // double + float instantiations (precision two-step) // By Claude on 07/13-14/2026
"pose_fx_jacobian", // one float thread per selected tile // By Codex on 07/15/2026 "pose_fx_jacobian", // one float thread per selected tile // By Codex on 07/15/2026
"pose_task_update", // 3-B rung B1: resident task build from the projection chain // By Claude on 07/16/2026
"pose_lma_step", // fixed 3-angle float products + direct solve in one thread // By Codex on 07/15/2026 "pose_lma_step", // fixed 3-angle float products + direct solve in one thread // By Codex on 07/15/2026
"pose_lma_prepare_products","pose_lma_reduce_step","pose_lma_reduce_rms", // resident prepare/solve + candidate RMS "pose_lma_prepare_products","pose_lma_reduce_step","pose_lma_reduce_rms", // resident prepare/solve + candidate RMS
"pose_lma_assemble","pose_lma_prepare_norm","pose_lma_prepare_finish", // rung C1 resident prepareLMA // By Claude on 07/17/2026 "pose_lma_assemble","pose_lma_prepare_norm","pose_lma_prepare_finish", // rung C1 resident prepareLMA // By Claude on 07/17/2026
...@@ -700,6 +701,15 @@ struct TpProc { ...@@ -700,6 +701,15 @@ struct TpProc {
int pose_prep_centers_tiles; // tile count the resident centers were uploaded for // By Claude on 07/17/2026 int pose_prep_centers_tiles; // tile count the resident centers were uploaded for // By Claude on 07/17/2026
int pose_prep_frozen_tiles; // rung C3: tile count of the frozen conditioning (0 = no full prepare yet) // By Claude on 07/17/2026 int pose_prep_frozen_tiles; // rung C3: tile count of the frozen conditioning (0 = no full prepare yet) // By Claude on 07/17/2026
PoseDpChain *gpu_pose_dp_chain; // DP spike: device copy of the chain descriptor // By Claude on 07/16/2026 PoseDpChain *gpu_pose_dp_chain; // DP spike: device copy of the chain descriptor // By Claude on 07/16/2026
// 3-B rung B1: per-sequence task template (packed txy + reference centers per
// selected task entry) for the resident task-build kernel. NULL template args
// on later calls mean "keep resident" (configuration-register semantics,
// rung C2 precedent). pose_task_count also validates the resident task-slot
// shape against the launch. By Claude on 07/16/2026.
int *gpu_pose_task_map;
float *gpu_pose_task_centers;
int pose_task_capacity; // template entries the buffers are sized for
int pose_task_count; // entries of the resident template (0 = none)
}; };
extern "C" { extern "C" {
...@@ -754,6 +764,8 @@ TpProc* tp_proc_create(TpModule* m){ ...@@ -754,6 +764,8 @@ TpProc* tp_proc_create(TpModule* m){
p->gpu_pose_lma_prep=nullptr; // By Claude on 07/17/2026 p->gpu_pose_lma_prep=nullptr; // By Claude on 07/17/2026
p->pose_prep_ready=0; p->pose_prep_centers_tiles=0; p->pose_prep_frozen_tiles=0; // By Claude on 07/17/2026 (rungs C2/C3) p->pose_prep_ready=0; p->pose_prep_centers_tiles=0; p->pose_prep_frozen_tiles=0; // By Claude on 07/17/2026 (rungs C2/C3)
p->gpu_pose_dp_chain=nullptr; // By Claude on 07/16/2026 (DP spike) p->gpu_pose_dp_chain=nullptr; // By Claude on 07/16/2026 (DP spike)
p->gpu_pose_task_map=nullptr; p->gpu_pose_task_centers=nullptr; // By Claude on 07/16/2026 (rung B1)
p->pose_task_capacity=0; p->pose_task_count=0; // By Claude on 07/16/2026 (rung B1)
return p; return p;
} }
...@@ -886,6 +898,17 @@ int tp_proc_get_tasks_slot(TpProc* p, float* out, int total_floats, int slot){ ...@@ -886,6 +898,17 @@ int tp_proc_get_tasks_slot(TpProc* p, float* out, int total_floats, int slot){
int tp_proc_get_tasks(TpProc* p, float* out, int total_floats){ int tp_proc_get_tasks(TpProc* p, float* out, int total_floats){
if(!p||(p->active_task_slot<0)) return -1; if(!p||(p->active_task_slot<0)) return -1;
return tp_proc_get_tasks_slot(p,out,total_floats,p->active_task_slot); } return tp_proc_get_tasks_slot(p,out,total_floats,p->active_task_slot); }
// 3-B rung B1: make a resident slot the live kernel task stream WITHOUT an H2D
// upload (set_tasks_slot minus the copy) - after pose_task_update rewrote the
// slot headers in place, geometry/convert consume it directly. The slot must
// hold a previously uploaded stream (the per-sequence skeleton) so its
// ntiles/floats bookkeeping is valid. By Claude on 07/16/2026.
int tp_proc_activate_tasks_slot(TpProc* p, int slot){
if(!p||(slot<0)||(slot>1)||!p->task_slots[slot]||(p->task_slot_ntiles[slot]<=0)){
seterr("activate_tasks_slot: empty slot %d",slot); return -1; }
p->active_task_slot=slot; p->ftasks=p->task_slots[slot]; p->ntiles=p->task_slot_ntiles[slot];
return 0;
}
// Upload to a named __constant__ symbol in the module (LPF filters: lpf_data / lpf_corr / lpf_rb_corr). // Upload to a named __constant__ symbol in the module (LPF filters: lpf_data / lpf_corr / lpf_rb_corr).
// Mirrors GpuQuad.setLpfRbg/setLpfCorr (cuModuleGetGlobal + cuMemcpyHtoD), against the native module. // Mirrors GpuQuad.setLpfRbg/setLpfCorr (cuModuleGetGlobal + cuMemcpyHtoD), against the native module.
...@@ -1377,6 +1400,159 @@ int tp_proc_exec_pose_fx_jacobian(TpProc* p, ...@@ -1377,6 +1400,159 @@ int tp_proc_exec_pose_fx_jacobian(TpProc* p,
return 0; return 0;
} }
// 3-B rung B1: resident task build. Launches pose_task_update over the
// per-sequence template, rewriting the 6-float headers of BOTH resident task
// slots from the device-authoritative pose + the per-scene uniform-MB
// descriptor. Nullable groups follow the rung-C2 configuration-register
// contract: camera blocks (per-sequence ref / per-scene scene, shared with
// prepare_resident via pose_prep_ready bits 1|2), the task template
// (per-sequence), and pose_vectors (NULL = keep the device current set; the
// production caller uploads the Java-tracked measure pose, 48 B). The task
// slots must hold the per-sequence skeleton (tp_proc_set_tasks_slot) with
// exactly num_tasks entries. By Claude on 07/16/2026.
int tp_proc_exec_pose_task_update(TpProc* p,
const float* ref_meta,
const float* ref_radial,
const float* ref_rbr, int ref_rbr_len,
const float* ref_ers, int ref_ers_len,
const float* scene_meta,
const float* scene_radial,
const float* scene_rbr, int scene_rbr_len,
const float* scene_ers, int scene_ers_len,
const float* pose_vectors,
const int* task_map,
const float* task_centers,
int num_tasks, int num_cams,
float disparity_corr,
float min_px, float max_px, float min_py, float max_py,
int task_code_main, int task_code_sub,
float dx_main, float dy_main, float dx_sub, float dy_sub,
float scale_main, float scale_sub){
const bool up_ref = (ref_meta!=nullptr)||(ref_radial!=nullptr)||(ref_rbr!=nullptr)||(ref_ers!=nullptr);
const bool up_scene = (scene_meta!=nullptr)||(scene_radial!=nullptr)||(scene_rbr!=nullptr)||(scene_ers!=nullptr);
const bool up_templ = (task_map!=nullptr)||(task_centers!=nullptr);
if(!p||(num_tasks<=0)||(num_cams<=0)||
(up_ref&&(!ref_meta||!ref_radial||!ref_rbr||!ref_ers||(ref_rbr_len<3)))||
(up_scene&&(!scene_meta||!scene_radial||!scene_rbr||!scene_ers||(scene_rbr_len<3)))||
(up_templ&&(!task_map||!task_centers))){
seterr("exec_pose_task_update: bad arguments n=%d",num_tasks); return -1; }
if((!up_ref&&!(p->pose_prep_ready&1))||(!up_scene&&!(p->pose_prep_ready&2))||
(!up_templ&&(p->pose_task_count!=num_tasks))||
(!pose_vectors&&!p->gpu_pose_vectors)){
seterr("exec_pose_task_update: resident group missing (ready=%d templ=%d/%d pose=%d)",
p->pose_prep_ready,p->pose_task_count,num_tasks,pose_vectors!=nullptr); return -1; }
// task_size from the skeleton the host uploaded (single source with Java's
// TpTask.getSize(); avoids re-deriving from struct tp_task at a second site)
if((p->task_slot_ntiles[0]!=num_tasks)||(p->task_slot_ntiles[1]!=num_tasks)||
(p->task_slot_floats[0]!=p->task_slot_floats[1])||
(p->task_slot_floats[0]<=0)||(p->task_slot_floats[0]%num_tasks)){
seterr("exec_pose_task_update: task slots not skeleton-loaded (n=%d/%d/%d floats=%d/%d)",
num_tasks,p->task_slot_ntiles[0],p->task_slot_ntiles[1],
p->task_slot_floats[0],p->task_slot_floats[1]); return -2; }
int task_size=p->task_slot_floats[0]/num_tasks;
if(task_size<(6+2*num_cams)){
seterr("exec_pose_task_update: task_size %d too small for %d cams",task_size,num_cams); return -2; }
if(up_ref){
const int ref_width=(int)std::lround(ref_meta[0]);
const int ref_height=(int)std::lround(ref_meta[1]);
if((ref_width<=0)||(ref_height<=0)||(ref_ers_len!=ref_height*POSE_ERS_LINE_STRIDE)||
!std::isfinite(ref_meta[7])||!(ref_meta[7]>0.0f)){
seterr("exec_pose_task_update: ref dimensions/ERS/step mismatch"); return -3; }
}
if(up_scene){
const int scene_width=(int)std::lround(scene_meta[0]);
const int scene_height=(int)std::lround(scene_meta[1]);
if((scene_width<=0)||(scene_height<=0)||(scene_ers_len!=scene_height*POSE_ERS_LINE_STRIDE)||
!std::isfinite(scene_meta[7])||!(scene_meta[7]>0.0f)){
seterr("exec_pose_task_update: scene dimensions/ERS/step mismatch"); return -3; }
}
cuCtxSetCurrent(p->mod->ctx);
#define POSE_ALLOC(field,count,type) do{ if(!(p->field) && \
cudaMalloc((void**)&(p->field),(size_t)(count)*sizeof(type))!=cudaSuccess){ \
seterr("exec_pose_task_update: alloc %s[%d] failed",#field,(int)(count)); return -4; } }while(0)
POSE_ALLOC(gpu_pose_cameras,2,PoseCameraMeta);
POSE_ALLOC(gpu_pose_radial[0],POSE_RADIAL_FLOATS,float);
POSE_ALLOC(gpu_pose_radial[1],POSE_RADIAL_FLOATS,float);
POSE_ALLOC(gpu_pose_vectors,4*POSE_NUM_PARAMS,float);
POSE_ALLOC(gpu_pose_candidate_vectors,4*POSE_NUM_PARAMS,float);
#undef POSE_ALLOC
if(!p->gpu_pose_current_set){
if(cudaMalloc((void**)&p->gpu_pose_current_set,sizeof(int))!=cudaSuccess ||
cudaMemset(p->gpu_pose_current_set,0,sizeof(int))!=cudaSuccess){
seterr("exec_pose_task_update: alloc current_set failed"); return -4; }
p->pose_current_set=0;
}
{ const int rbr_lengths[2]={up_ref?ref_rbr_len:0,up_scene?scene_rbr_len:0};
const int ers_lengths[2]={up_ref?ref_ers_len:0,up_scene?scene_ers_len:0};
for(int camera=0;camera<2;camera++){
if(rbr_lengths[camera]>p->pose_rbr_capacity[camera]){
cudaFree(p->gpu_pose_rbr[camera]); p->gpu_pose_rbr[camera]=nullptr; p->pose_rbr_capacity[camera]=0;
if(cudaMalloc((void**)&p->gpu_pose_rbr[camera],(size_t)rbr_lengths[camera]*sizeof(float))!=cudaSuccess){
seterr("exec_pose_task_update: alloc rbr[%d] failed",camera); return -4; }
p->pose_rbr_capacity[camera]=rbr_lengths[camera];
}
if(ers_lengths[camera]>p->pose_ers_capacity[camera]){
cudaFree(p->gpu_pose_ers[camera]); p->gpu_pose_ers[camera]=nullptr; p->pose_ers_capacity[camera]=0;
if(cudaMalloc((void**)&p->gpu_pose_ers[camera],(size_t)ers_lengths[camera]*sizeof(float))!=cudaSuccess){
seterr("exec_pose_task_update: alloc ers[%d] failed",camera); return -4; }
p->pose_ers_capacity[camera]=ers_lengths[camera];
}
}
}
if(up_templ&&(num_tasks>p->pose_task_capacity)){
cudaFree(p->gpu_pose_task_map); cudaFree(p->gpu_pose_task_centers);
p->gpu_pose_task_map=nullptr; p->gpu_pose_task_centers=nullptr;
p->pose_task_capacity=0; p->pose_task_count=0;
if(cudaMalloc((void**)&p->gpu_pose_task_map,(size_t)num_tasks*sizeof(int))!=cudaSuccess ||
cudaMalloc((void**)&p->gpu_pose_task_centers,(size_t)3*num_tasks*sizeof(float))!=cudaSuccess){
seterr("exec_pose_task_update: template alloc failed n=%d",num_tasks); return -4; }
p->pose_task_capacity=num_tasks;
}
if(up_ref){
const PoseCameraMeta href={(int)std::lround(ref_meta[0]),(int)std::lround(ref_meta[1]),ref_rbr_len,
ref_meta[2],ref_meta[3],ref_meta[4],ref_meta[5],ref_meta[6],ref_meta[7]};
if(cudaMemcpy(p->gpu_pose_cameras,&href,sizeof(href),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_radial[0],ref_radial,POSE_RADIAL_FLOATS*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_rbr[0],ref_rbr,(size_t)ref_rbr_len*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_ers[0],ref_ers,(size_t)ref_ers_len*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess){
seterr("exec_pose_task_update: ref HtoD failed"); return -5; }
p->pose_prep_ready|=1;
}
if(up_scene){
const PoseCameraMeta hscene={(int)std::lround(scene_meta[0]),(int)std::lround(scene_meta[1]),scene_rbr_len,
scene_meta[2],scene_meta[3],scene_meta[4],scene_meta[5],scene_meta[6],scene_meta[7]};
if(cudaMemcpy(p->gpu_pose_cameras+1,&hscene,sizeof(hscene),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_radial[1],scene_radial,POSE_RADIAL_FLOATS*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_rbr[1],scene_rbr,(size_t)scene_rbr_len*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_ers[1],scene_ers,(size_t)scene_ers_len*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess){
seterr("exec_pose_task_update: scene HtoD failed"); return -5; }
p->pose_prep_ready|=2;
}
if(up_templ){
if(cudaMemcpy(p->gpu_pose_task_map,task_map,(size_t)num_tasks*sizeof(int),cudaMemcpyHostToDevice)!=cudaSuccess ||
cudaMemcpy(p->gpu_pose_task_centers,task_centers,(size_t)3*num_tasks*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess){
seterr("exec_pose_task_update: template HtoD failed"); return -5; }
p->pose_task_count=num_tasks;
}
if(pose_vectors&&
cudaMemcpy(p->cur_pose_vectors(),pose_vectors,(size_t)4*POSE_NUM_PARAMS*sizeof(float),cudaMemcpyHostToDevice)!=cudaSuccess){
seterr("exec_pose_task_update: pose HtoD failed"); return -5; }
CUfunction f=getfun(p->mod,"pose_task_update");
if(!f){ seterr("pose_task_update missing"); return -6; }
float* cur_vectors=p->cur_pose_vectors(); // device-authoritative current set (rung B)
void* args[]={ &num_tasks,&task_size,&p->gpu_pose_cameras,
&p->gpu_pose_radial[0],&p->gpu_pose_rbr[0],&p->gpu_pose_ers[0],
&p->gpu_pose_radial[1],&p->gpu_pose_ers[1],
&cur_vectors,&p->gpu_pose_task_map,&p->gpu_pose_task_centers,
&disparity_corr,&min_px,&max_px,&min_py,&max_py,
&task_code_main,&task_code_sub,
&dx_main,&dy_main,&dx_sub,&dy_sub,&scale_main,&scale_sub,
&p->task_slots[0],&p->task_slots[1] };
const int threads=256;
if(launch1(f,(num_tasks+threads-1)/threads,1,1,threads,1,1,args,"pose_task_update")) return -7;
return 0;
}
// Primitive-float fixed three-angle candidate calculation. This validation // Primitive-float fixed three-angle candidate calculation. This validation
// boundary deliberately uploads transformed J/W/residual state on every call; // boundary deliberately uploads transformed J/W/residual state on every call;
// the final resident chain will produce and consume those arrays on-device. // the final resident chain will produce and consume those arrays on-device.
...@@ -2168,6 +2344,7 @@ void tp_proc_destroy(TpProc* p){ ...@@ -2168,6 +2344,7 @@ void tp_proc_destroy(TpProc* p){
cudaFree(p->gpu_pose_lma_candidate_partials); // By Codex on 07/15/2026 cudaFree(p->gpu_pose_lma_candidate_partials); // By Codex on 07/15/2026
cudaFree(p->gpu_pose_lma_prep); // By Claude on 07/17/2026 cudaFree(p->gpu_pose_lma_prep); // By Claude on 07/17/2026
cudaFree(p->gpu_pose_dp_chain); // By Claude on 07/16/2026 (DP spike) cudaFree(p->gpu_pose_dp_chain); // By Claude on 07/16/2026 (DP spike)
cudaFree(p->gpu_pose_task_map); cudaFree(p->gpu_pose_task_centers); // By Claude on 07/16/2026 (rung B1)
delete p; delete p;
} }
......
// test_pose_task_update.cu - standalone (nvcc-direct, cuda-gdb steppable) gate
// for the 3-B rung B1 resident task-build kernel. Fully synthetic in-process
// (identity camera, no data files), three tiers:
// 1. SERIAL ORACLE, bit-exact: the kernel body is a grid-stride loop, so a
// <<<1,1>>> launch of the SAME kernel is the serial float oracle
// (pose_lma_prepare_norm/finish precedent). The parallel launch must
// memcmp-match it exactly.
// 2. PROJECTION CONSISTENCY: centerXY_main - dx_main must equal the
// C1-validated pose_fx_jacobian fx for every surviving tile (same
// __device__ projection chain; small tolerance only for cross-kernel
// compiler scheduling).
// 3. GATE/ENCODING LOGIC, bit-exact: task words, txy, scale fields and the
// hole defaults recomputed on the host from the fx readback.
//
// Build: src/tests/build_tests.sh test_pose_task_update
// Run: tests_bin/test_pose_task_update
//
// Created Jul 16, 2026 by Claude (Anthropic) for Elphel - 3-B rung B1.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <cuda_runtime.h>
#include "tp_defines.h"
#include "geometry_correction.h"
#include "tp_lma.h"
#define CK(call) do{ cudaError_t e=(call); if(e!=cudaSuccess){ \
std::fprintf(stderr,"%s -> %s\n",#call,cudaGetErrorString(e)); return EXIT_FAILURE; } }while(0)
int main()
{
// Identity synthetic camera (test_pose_fx_jacobian_jna precedent): zero
// radial, unit rbr, identity per-line ERS quaternion, zero pose.
const int width=64,height=48,rbr_len=256,ers_len=height*POSE_ERS_LINE_STRIDE;
const PoseCameraMeta hcam={width,height,rbr_len,4.0f,5.0f,2.85f,1.0f,0.0f,0.0004f};
const PoseCameraMeta hcams[2]={hcam,hcam};
const float hradial[POSE_RADIAL_FLOATS]={0,0,0,0,0,0,0};
std::vector<float> hrbr(rbr_len,1.0f),hers(ers_len,0.0f);
for(int line=0;line<height;line++) hers[(size_t)line*POSE_ERS_LINE_STRIDE+6]=1.0f; // +6 = POSE_ERS_QUAT w (tp_lma.cu)
const float hpose[4*POSE_NUM_PARAMS]={0,0,0, 0,0,0, 0,0,0, 0,0,0};
// Template: a mix of interior tiles, margin-straddling tiles, an infinity
// tile, a NaN hole and centers far outside the sensor.
const int num_tasks=257; // > one 256-thread block
const int num_cams=1;
const int task_size=6+2*num_cams+4*num_cams; // 12 floats (kernel treats it as a stride)
std::vector<int> hmap(num_tasks);
std::vector<float> hcenters(3*num_tasks);
for(int i=0;i<num_tasks;i++){
const int tx=i%16, ty=i/16;
hmap[i]=tx|(ty<<16);
float px=3.0f+(i%59)*1.05f; // 3..64 - crosses the margin gate and the sensor edge
float py=2.0f+(i%43)*1.1f; // 2..48
float disparity=(i%5==0)?0.0f:(0.5f+(i%7)); // infinity + finite mix
if(i%29==7){ px=nanf(""); } // NaN hole
hcenters[3*i]=px; hcenters[3*i+1]=py; hcenters[3*i+2]=disparity;
}
const float disparity_corr=0.125f;
const float min_px=2.0f,max_px=width-1-2.0f,min_py=2.0f,max_py=height-1-2.0f;
const int task_code_main=((1<<TASK_CORR_EN)|(1<<TASK_INTER_EN))|511;
const int task_code_sub=task_code_main;
const float dx_main=-0.25f,dy_main=-0.1f,dx_sub=0.75f,dy_sub=0.3f;
const float scale_main=2.5f,scale_sub=-1.5f;
PoseCameraMeta* dcams; float *dradial,*drbr,*ders,*dpose,*dcenters; int* dmap;
CK(cudaMalloc(&dcams,sizeof(hcams)));
CK(cudaMalloc(&dradial,sizeof(hradial)));
CK(cudaMalloc(&drbr,rbr_len*sizeof(float)));
CK(cudaMalloc(&ders,ers_len*sizeof(float)));
CK(cudaMalloc(&dpose,sizeof(hpose)));
CK(cudaMalloc(&dmap,num_tasks*sizeof(int)));
CK(cudaMalloc(&dcenters,3*num_tasks*sizeof(float)));
CK(cudaMemcpy(dcams,hcams,sizeof(hcams),cudaMemcpyHostToDevice));
CK(cudaMemcpy(dradial,hradial,sizeof(hradial),cudaMemcpyHostToDevice));
CK(cudaMemcpy(drbr,hrbr.data(),rbr_len*sizeof(float),cudaMemcpyHostToDevice));
CK(cudaMemcpy(ders,hers.data(),ers_len*sizeof(float),cudaMemcpyHostToDevice));
CK(cudaMemcpy(dpose,hpose,sizeof(hpose),cudaMemcpyHostToDevice));
CK(cudaMemcpy(dmap,hmap.data(),num_tasks*sizeof(int),cudaMemcpyHostToDevice));
CK(cudaMemcpy(dcenters,hcenters.data(),3*num_tasks*sizeof(float),cudaMemcpyHostToDevice));
const size_t slot_floats=(size_t)num_tasks*task_size;
float *dtasks0,*dtasks1,*stasks0,*stasks1; // parallel + serial-oracle slot pairs
CK(cudaMalloc(&dtasks0,slot_floats*sizeof(float)));
CK(cudaMalloc(&dtasks1,slot_floats*sizeof(float)));
CK(cudaMalloc(&stasks0,slot_floats*sizeof(float)));
CK(cudaMalloc(&stasks1,slot_floats*sizeof(float)));
CK(cudaMemset(dtasks0,0x5a,slot_floats*sizeof(float))); // distinct garbage: headers must be
CK(cudaMemset(dtasks1,0x5a,slot_floats*sizeof(float))); // fully overwritten either way
CK(cudaMemset(stasks0,0xa5,slot_floats*sizeof(float)));
CK(cudaMemset(stasks1,0xa5,slot_floats*sizeof(float)));
const int threads=256;
pose_task_update<<<(num_tasks+threads-1)/threads,threads>>>(
num_tasks,task_size,dcams,dradial,drbr,ders,dradial,ders,dpose,dmap,dcenters,
disparity_corr,min_px,max_px,min_py,max_py,task_code_main,task_code_sub,
dx_main,dy_main,dx_sub,dy_sub,scale_main,scale_sub,dtasks0,dtasks1);
CK(cudaGetLastError()); CK(cudaDeviceSynchronize());
pose_task_update<<<1,1>>>( // the serial float oracle
num_tasks,task_size,dcams,dradial,drbr,ders,dradial,ders,dpose,dmap,dcenters,
disparity_corr,min_px,max_px,min_py,max_py,task_code_main,task_code_sub,
dx_main,dy_main,dx_sub,dy_sub,scale_main,scale_sub,stasks0,stasks1);
CK(cudaGetLastError()); CK(cudaDeviceSynchronize());
// fx reference from the C1-validated chain (all tiles selected)
std::vector<unsigned char> hsel(num_tasks,1);
unsigned char* dsel; float *dfx; unsigned char* dvalid;
CK(cudaMalloc(&dsel,num_tasks));
CK(cudaMalloc(&dfx,(size_t)POSE_NUM_COMPONENTS*num_tasks*sizeof(float)));
CK(cudaMalloc(&dvalid,num_tasks));
CK(cudaMemcpy(dsel,hsel.data(),num_tasks,cudaMemcpyHostToDevice));
pose_fx_jacobian<<<(num_tasks+threads-1)/threads,threads>>>(
num_tasks,0,dcams,dradial,drbr,ders,dradial,drbr,ders,dpose,dcenters,dsel,
dfx,nullptr,dvalid);
CK(cudaGetLastError()); CK(cudaDeviceSynchronize());
std::vector<float> t0(slot_floats),t1(slot_floats),s0(slot_floats),s1(slot_floats);
std::vector<float> fx((size_t)POSE_NUM_COMPONENTS*num_tasks);
std::vector<unsigned char> valid(num_tasks);
CK(cudaMemcpy(t0.data(),dtasks0,slot_floats*sizeof(float),cudaMemcpyDeviceToHost));
CK(cudaMemcpy(t1.data(),dtasks1,slot_floats*sizeof(float),cudaMemcpyDeviceToHost));
CK(cudaMemcpy(s0.data(),stasks0,slot_floats*sizeof(float),cudaMemcpyDeviceToHost));
CK(cudaMemcpy(s1.data(),stasks1,slot_floats*sizeof(float),cudaMemcpyDeviceToHost));
CK(cudaMemcpy(fx.data(),dfx,fx.size()*sizeof(float),cudaMemcpyDeviceToHost));
CK(cudaMemcpy(valid.data(),dvalid,num_tasks,cudaMemcpyDeviceToHost));
int bad=0;
// Tier 1: serial oracle, header floats bit-exact (tails keep their distinct
// garbage fills by design - the kernel must not touch xy/disp_dist).
for(int i=0;i<num_tasks;i++){
const float* pt[2]={&t0[(size_t)i*task_size],&t1[(size_t)i*task_size]};
const float* ps[2]={&s0[(size_t)i*task_size],&s1[(size_t)i*task_size]};
for(int slot=0;slot<2;slot++){
if(std::memcmp(pt[slot],ps[slot],6*sizeof(float))){
if(bad<5) std::printf("task %d slot %d: parallel != serial oracle\n",i,slot);
bad++;
}
for(int k=6;k<task_size;k++){
const float garb_p=pt[slot][k],garb_s=ps[slot][k];
if((*(const unsigned*)&garb_p)!=0x5a5a5a5au||(*(const unsigned*)&garb_s)!=0xa5a5a5a5u){
if(bad<5) std::printf("task %d slot %d: tail float %d was touched\n",i,slot,k);
bad++;
}
}
}
}
// Tiers 2+3: recompute the gate on the host from the fx readback.
int survivors=0,holes=0; float max_center_err=0.0f;
for(int i=0;i<num_tasks;i++){
const float* h0=&t0[(size_t)i*task_size];
const float* h1=&t1[(size_t)i*task_size];
if(*(const int*)(h0+TP_TASK_TXY_OFFSET)!=hmap[i]||
*(const int*)(h1+TP_TASK_TXY_OFFSET)!=hmap[i]){
if(bad<5) std::printf("task %d: txy mismatch\n",i);
bad++;
}
bool expect_survive=false;
if(valid[i]){
const float sx=fx[(size_t)POSE_NUM_COMPONENTS*i],sy=fx[(size_t)POSE_NUM_COMPONENTS*i+1];
const float cxm=sx+dx_main,cym=sy+dy_main,cxs=sx+dx_sub,cys=sy+dy_sub;
expect_survive=!((cxm<min_px)||(cxm>max_px)||(cym<min_py)||(cym>max_py)||
(cxs<min_px)||(cxs>max_px)||(cys<min_py)||(cys>max_py));
if(expect_survive){
const float e0=std::fabs(h0[TP_TASK_CENTERXY_OFFSET]-cxm);
const float e1=std::fabs(h0[TP_TASK_CENTERXY_OFFSET+1]-cym);
const float e2=std::fabs(h1[TP_TASK_CENTERXY_OFFSET]-cxs);
const float e3=std::fabs(h1[TP_TASK_CENTERXY_OFFSET+1]-cys);
const float err=std::fmax(std::fmax(e0,e1),std::fmax(e2,e3));
if(err>max_center_err) max_center_err=err;
if(err>1.0e-4f){ if(bad<5) std::printf("task %d: centerXY vs fx err %g\n",i,err); bad++; }
}
}
const int tw0=*(const int*)(h0+TP_TASK_TASK_OFFSET);
const int tw1=*(const int*)(h1+TP_TASK_TASK_OFFSET);
if(expect_survive){
// fx and the kernel run the same projection; a borderline center could in
// principle flip the gate between kernels, but the synthetic margins are
// not that tight - treat any word mismatch as failure.
if(tw0!=task_code_main||tw1!=task_code_sub){
if(bad<5) std::printf("task %d: task words %08x/%08x (expected survive)\n",i,tw0,tw1);
bad++;
} else {
if(h0[TP_TASK_SCALE_OFFSET]!=scale_main||h1[TP_TASK_SCALE_OFFSET]!=scale_sub){
if(bad<5) std::printf("task %d: scale mismatch\n",i);
bad++;
}
if(h0[TP_TASK_DISPARITY_OFFSET]!=h1[TP_TASK_DISPARITY_OFFSET]){
if(bad<5) std::printf("task %d: target_disparity differs between sets\n",i);
bad++;
}
survivors++;
}
} else {
if(tw0!=0||tw1!=0||
h0[TP_TASK_DISPARITY_OFFSET]!=0.0f||h1[TP_TASK_DISPARITY_OFFSET]!=0.0f||
h0[TP_TASK_CENTERXY_OFFSET]!=0.0f||h0[TP_TASK_CENTERXY_OFFSET+1]!=0.0f||
h1[TP_TASK_CENTERXY_OFFSET]!=0.0f||h1[TP_TASK_CENTERXY_OFFSET+1]!=0.0f||
h0[TP_TASK_SCALE_OFFSET]!=0.0f||h1[TP_TASK_SCALE_OFFSET]!=0.0f){
if(bad<5) std::printf("task %d: hole not zeroed (words %08x/%08x)\n",i,tw0,tw1);
bad++;
} else holes++;
}
}
if(survivors==0||holes==0){
std::printf("degenerate case coverage: survivors=%d holes=%d\n",survivors,holes);
bad++;
}
std::printf("pose_task_update: %d tasks (%d survive, %d holes), serial-oracle bit-exact, "
"max|centerXY-fx-descriptor| = %g px -> %s\n",
num_tasks,survivors,holes,max_center_err,bad?"FAIL":"PASS");
return bad?EXIT_FAILURE:EXIT_SUCCESS;
}
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
#ifndef JCUDA #ifndef JCUDA
#include <cuda_runtime.h> #include <cuda_runtime.h>
#include "tp_defines.h" // NUM_CAMS (for geometry_correction.h) // By Claude on 07/16/2026
#include "geometry_correction.h" // TP_TASK_*_OFFSET task-stream field offsets (pose_task_update) // By Claude on 07/16/2026
#include "tp_lma.h" #include "tp_lma.h"
#endif #endif
...@@ -473,6 +475,110 @@ extern "C" __global__ void pose_fx_jacobian( ...@@ -473,6 +475,110 @@ extern "C" __global__ void pose_fx_jacobian(
valid[tile] = 1; valid[tile] = 1;
} }
// 3-B rung B1 (design 2026-07-16): resident task build. One thread per selected
// task template entry runs the SAME C1-validated projection chain as
// pose_fx_jacobian (pose_world_from_pixel -> pose_pixel_from_world) and writes
// the 6-float task headers of BOTH resident ftask slots in place: slot 0 = MB
// main, slot 1 = MB partner. All per-tile variation comes from the projection;
// the uniform-MB contribution is the per-scene 6-float descriptor
// {dx_main, dy_main, dx_sub, dy_sub, scale_main, scale_sub} applied as
// constants (the crank math is tile-invariant under pose_mb_uniform). Task
// codes arrive pre-encoded from the host (already |511, matching
// GpuQuadJna.setTasks): no-MB mode = task_code_sub 511 and a zero descriptor.
// A tile whose projection fails or whose main/sub center falls outside the
// margin gate is written as a HOLE: task=0 in both sets (a value the Java
// upload path never produces) - index_direct skips it in convert_direct,
// index_consolidate drops it, the inter correlation never lists it, so
// downstream it is a missing peak and the per-cycle conditioning abstains
// (the Java build reached the same end by removing the entry from the
// condensed stream). xy/disp_dist tails are untouched - calculate_tiles_offsets
// overwrites them for every entry as before. The body is a grid-stride loop:
// a <<<1,1>>> launch of the SAME kernel is the bit-exact serial oracle
// (pose_lma_prepare_norm/finish precedent). By Claude on 07/16/2026.
extern "C" __global__ void pose_task_update(
int num_tasks,
int task_size, // floats per task entry (get_task_size(num_cams))
const PoseCameraMeta * cameras, // reference, scene
const float * ref_radial, // [7]
const float * ref_rbr,
const float * ref_ers, // [height][14]
const float * scene_radial, // [7]
const float * scene_ers, // [height][14]
const float * pose_vectors, // ref xyz/atr, scene xyz/atr: 4x3 (device current set)
const int * task_map, // [num_tasks] packed txy: tx | (ty<<16) (per-sequence template)
const float * task_centers, // [3*num_tasks] reference px,py,disparity (per-sequence template)
float disparity_corr,
float min_px, // margin gate (host-computed Java bounds)
float max_px,
float min_py,
float max_py,
int task_code_main, // pre-encoded (|511) task word, set 0
int task_code_sub, // pre-encoded task word, set 1 (511 = disabled no-MB partner)
float dx_main, // uniform-MB descriptor: centerXY offsets from the projected point
float dy_main,
float dx_sub,
float dy_sub,
float scale_main, // pre-encoded scale fields (Java setScale semantics)
float scale_sub,
float * ftasks0, // resident task slot 0 (MB main)
float * ftasks1) // resident task slot 1 (MB partner)
{
__shared__ float matrices[2 * 36];
if (threadIdx.x == 0) {
pose_rotation_matrices(pose_vectors + 3, true, matrices); // reference inverse (+derivs)
pose_rotation_matrices(pose_vectors + 9, false, matrices + 36); // scene direct
}
__syncthreads();
for (int task = (int) (blockIdx.x * blockDim.x + threadIdx.x); task < num_tasks;
task += (int) (gridDim.x * blockDim.x)) {
float * t0 = ftasks0 + (size_t) task * task_size;
float * t1 = ftasks1 + (size_t) task * task_size;
const int txy = task_map[task];
*(int *) (t0 + TP_TASK_TXY_OFFSET) = txy;
*(int *) (t1 + TP_TASK_TXY_OFFSET) = txy;
// hole defaults first; overwritten below when the tile survives
*(int *) (t0 + TP_TASK_TASK_OFFSET) = 0;
*(int *) (t1 + TP_TASK_TASK_OFFSET) = 0;
t0[TP_TASK_DISPARITY_OFFSET] = 0.0f;
t1[TP_TASK_DISPARITY_OFFSET] = 0.0f;
t0[TP_TASK_CENTERXY_OFFSET] = 0.0f; t0[TP_TASK_CENTERXY_OFFSET + 1] = 0.0f;
t1[TP_TASK_CENTERXY_OFFSET] = 0.0f; t1[TP_TASK_CENTERXY_OFFSET + 1] = 0.0f;
t0[TP_TASK_SCALE_OFFSET] = 0.0f;
t1[TP_TASK_SCALE_OFFSET] = 0.0f;
const int ci = 3 * task;
const float px = task_centers[ci];
const float py = task_centers[ci + 1];
const float disparity = task_centers[ci + 2];
if (!isfinite(px) || !isfinite(py) || !isfinite(disparity)) continue;
const bool infinity = disparity < POSE_INFINITY_DISPARITY;
float world[3], d_pixel[9], d_atr[9];
if (!pose_world_from_pixel(cameras, ref_radial, ref_rbr, ref_ers,
px, py, disparity, infinity, pose_vectors, matrices,
world, d_pixel, d_atr)) continue;
float scene_pixel[3];
if (!pose_pixel_from_world(cameras + 1, scene_radial, scene_ers,
world, infinity, pose_vectors + 6, matrices + 36, scene_pixel)) continue;
const float cx_main = scene_pixel[0] + dx_main;
const float cy_main = scene_pixel[1] + dy_main;
const float cx_sub = scene_pixel[0] + dx_sub;
const float cy_sub = scene_pixel[1] + dy_sub;
// Java setInterTasksMotionBlur margin gate: main AND sub centers inside
if ((cx_main < min_px) || (cx_main > max_px) || (cy_main < min_py) || (cy_main > max_py) ||
(cx_sub < min_px) || (cx_sub > max_px) || (cy_sub < min_py) || (cy_sub > max_py)) continue;
const float target_disparity = scene_pixel[2] + disparity_corr;
if (!isfinite(target_disparity)) continue;
*(int *) (t0 + TP_TASK_TASK_OFFSET) = task_code_main;
*(int *) (t1 + TP_TASK_TASK_OFFSET) = task_code_sub;
t0[TP_TASK_DISPARITY_OFFSET] = target_disparity;
t1[TP_TASK_DISPARITY_OFFSET] = target_disparity;
t0[TP_TASK_CENTERXY_OFFSET] = cx_main; t0[TP_TASK_CENTERXY_OFFSET + 1] = cy_main;
t1[TP_TASK_CENTERXY_OFFSET] = cx_sub; t1[TP_TASK_CENTERXY_OFFSET + 1] = cy_sub;
t0[TP_TASK_SCALE_OFFSET] = scale_main;
t1[TP_TASK_SCALE_OFFSET] = scale_sub;
}
}
static __device__ __forceinline__ float pose_lma_mul(float a, float b) static __device__ __forceinline__ float pose_lma_mul(float a, float b)
{ {
return __fmul_rn(a, b); return __fmul_rn(a, b);
......
...@@ -79,6 +79,38 @@ extern "C" __global__ void pose_fx_jacobian( ...@@ -79,6 +79,38 @@ extern "C" __global__ void pose_fx_jacobian(
float * jt, // parameter-major [3][2*num_tiles], nullable float * jt, // parameter-major [3][2*num_tiles], nullable
unsigned char * valid); unsigned char * valid);
// 3-B rung B1: resident task build - the pose_fx_jacobian projection chain
// writes the 6-float task headers of both resident ftask slots from a
// per-sequence template + a per-scene uniform-MB descriptor. Grid-stride body:
// a <<<1,1>>> launch is the bit-exact serial oracle. By Claude on 07/16/2026.
extern "C" __global__ void pose_task_update(
int num_tasks,
int task_size,
const PoseCameraMeta * cameras, // reference, scene
const float * ref_radial, // [7]
const float * ref_rbr,
const float * ref_ers, // [height][14]
const float * scene_radial, // [7]
const float * scene_ers, // [height][14]
const float * pose_vectors, // ref xyz/atr, scene xyz/atr: 4x3
const int * task_map, // [num_tasks] packed txy
const float * task_centers, // [3*num_tasks] reference px,py,disparity
float disparity_corr,
float min_px,
float max_px,
float min_py,
float max_py,
int task_code_main,
int task_code_sub,
float dx_main,
float dy_main,
float dx_sub,
float dy_sub,
float scale_main,
float scale_sub,
float * ftasks0,
float * ftasks1);
// Fixed three-angle candidate calculation in one float thread. Output layout: // Fixed three-angle candidate calculation in one float thread. Output layout:
// {valid, raw H[9], raw b[3], delta[3], candidate[3]}. // {valid, raw H[9], raw b[3], delta[3], candidate[3]}.
extern "C" __global__ void pose_lma_step( extern "C" __global__ void pose_lma_step(
......
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