nx_game_texture_mip_emit_t241.nx source
↩ module page · 16 lines · 5184 B
1import "nx_wgsl.nx"
2import "nx_tex_sample_mip_alpha_candidate_t241.nx"
3// Emit mip shader from native IR during the same page build; no browser-authored shader.
4func gpe_mip_shader(out:*u8,pos:i64,m:*i64,stage:i64,name:*u8)->i64{
5 let buf:*u8=sys_mmap(WG_EMIT_CAP);let trace:*i64=sys_mmap(WG_TRACE_CAP*8) as *i64;let count:*i64=sys_mmap(8) as *i64;count[0]=0
6 let n:i64=wgsl_emit_module(m,stage,buf,WG_EMIT_CAP,trace,WG_TRACE_CAP,count)
7 var p:i64=pos
8 if n<=0||sir_refused(m)!=0{gsb_failed=1}else{p=gpe_ap(out,p,"const ");p=gpe_ap(out,p,name);p=gpe_ap(out,p,"=");p=rk_js_str(out,p,buf,n);p=gpe_ap(out,p,";\n")}
9 sys_munmap(buf,WG_EMIT_CAP);sys_munmap(trace as *u8,WG_TRACE_CAP*8);sys_munmap(count as *u8,8);return p
10}
11func gpe_mip_emit(out:*u8,pos:i64)->i64{
12 let v:*i64=sir_new();shsrc_fullscreen_tri(v);var p:i64=gpe_mip_shader(out,pos,v,STAGE_VERTEX,"NXMIP_VERTEX")
13 p=gpe_mip_shader(out,p,nx_txs_mip_fragment_alpha(1),STAGE_FRAGMENT,"NXMIP_FRAGMENT")
14 p=gpe_mip_shader(out,p,nx_txs_mip_fragment_alpha(0),STAGE_FRAGMENT,"NXMIP_NORMAL_FRAGMENT")
15 return gpe_ap(out,p,"function nxMipPlan(P){const w=P.image.width,h=P.image.height,min=P.sampler?.min,wants=[9984,9985,9986,9987].includes(min),eligible=wants&&(P.textureRole==='normal'||P.material?.alpha===2);let W=w,H=h,levels=1,bytes=w*h*4;while(eligible&&(W>1||H>1)){W=Math.max(1,Math.floor(W/2));H=Math.max(1,Math.floor(H/2));levels++;bytes+=W*H*4;}return {role:P.textureRole==='normal'?'normal':'color',state:eligible?'eligible':'source-level-only',reason:eligible?(P.textureRole==='normal'?'native-linear-unweighted-normal-components':'native-linear-alpha-weighted'):wants&&P.material?.alpha===1?'MASK coverage and silhouette unqualified':wants?'material filtering unqualified':'source sampler requests no mip levels',levels,baseBytes:w*h*4,textureBytes:bytes,extraBytes:bytes-w*h*4,passes:levels-1,width:w,height:h,sourceMinFilter:min};}\nasync function nxMipGenerate(owner,D,owned){const plan=D.textureSampling;if(plan.levels===1)return;const dev=owner.dev,start=performance.now();const normal=plan.role==='normal',mipFormat=normal?'rgba8unorm':'rgba8unorm-srgb',cache=normal?'normalMipPipeline':'mipPipeline';if(!owner[cache]){const v=dev.createShaderModule({code:NXMIP_VERTEX}),f=dev.createShaderModule({code:normal?NXMIP_NORMAL_FRAGMENT:NXMIP_FRAGMENT});owner[cache]=dev.createRenderPipelineAsync({layout:'auto',vertex:{module:v,entryPoint:'main'},fragment:{module:f,entryPoint:'main',targets:[{format:mipFormat}]}});}const pipeline=await owner[cache],stride=dev.limits.minUniformBufferOffsetAlignment,size=(plan.levels-1)*stride,ub=dev.createBuffer({size,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});owned.push(ub);const values=new Float32Array(size/4),enc=dev.createCommandEncoder();let w=plan.width,h=plan.height;for(let level=1;level<plan.levels;level++){const nw=Math.max(1,Math.floor(w/2)),nh=Math.max(1,Math.floor(h/2)),offset=(level-1)*stride;values.set([w,h,nw,nh],offset/4);const group=dev.createBindGroup({layout:pipeline.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:ub,offset,size:16}},{binding:1,resource:D.atl.createView({format:mipFormat,baseMipLevel:level-1,mipLevelCount:1})}]});const pass=enc.beginRenderPass({colorAttachments:[{view:D.atl.createView({format:mipFormat,baseMipLevel:level,mipLevelCount:1}),loadOp:'clear',storeOp:'store',clearValue:[0,0,0,0]}]});pass.setPipeline(pipeline);pass.setBindGroup(0,group);pass.draw(3);pass.end();w=nw;h=nh;}dev.queue.writeBuffer(ub,0,values);dev.queue.submit([enc.finish()]);plan.state='submitted';plan.cpuPrepareSubmitMs=performance.now()-start;plan.uniformBytes=size;plan.timingScope='CPU preparation and queue submit; not GPU duration';plan.alphaContract=normal?'unweighted linear encoded components; final shader normalizes; no roughness variance reconstruction':'linear alpha weighted RGB, straight output; runtime straight-alpha interpolation unchanged';plan.level0='unchanged source image copy';}\nfunction nxMipControls(box){const details=document.createElement('details'),label=document.createElement('summary'),button=document.createElement('button'),readout=document.createElement('pre');label.textContent='Texture sampling evidence';button.type='button';button.textContent='Compare hair: source level only';let enabled=true;const refresh=()=>{readout.textContent=JSON.stringify(DN.filter(d=>d.prepared).map(d=>({asset:d.sourceId,sourceSha256:d.prepared.manifest.source.sha256,parts:d.gpu.parts.map(p=>({...p.textureSampling,normal:p.normal?.textureSampling||p.normalAdmission||null}))})),null,2);};button.onclick=()=>{enabled=!enabled;for(const d of DN)for(const p of d.gpu?.parts||[]){if(p.textureSampling?.levels>1){p.samp=GPU.dev.createSampler({...p.samplerDescriptor,lodMaxClamp:enabled?p.textureSampling.levels-1:0});p.bgv=null;p.bgf=null;p.textureSampling.selected=enabled?'mips':'source-level';}}button.textContent=enabled?'Compare hair: source level only':'Compare hair: native mip filtering';refresh();};details.ontoggle=()=>{if(details.open)refresh();};readout.style.cssText='max-height:30vh;overflow:auto;white-space:pre-wrap;max-width:100%';details.append(label,button,readout);box.append(details);}\n" as *u8)
16}